Quiescent Search (and Sorting MoveList)

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

CheckersGuy
Posts: 273
Joined: Wed Aug 24, 2016 9:49 pm

Quiescent Search (and Sorting MoveList)

Post by CheckersGuy »

Hey guys,
I have implemented QuiesceneSearch but I it didn't seem to help my engine nor did it hurt my engine in any way. I just wonder if my implementation is correct if that's not the case there is probably a bug somewhere else. (I am using failSort implementation of PVS)

Here is the implementation I am using:

Code: Select all

public static int quiesceneSearch(Board board, int alpha, int beta){
        
        final int standpat =board.color*GameEvaluation.evaluate(board);
        if(standpat>=beta)
            return beta;
        if&#40;alpha<standpat&#41;
            alpha=standpat;

        ArrayList<Move> jumpers = MGenerator.getCaptures&#40;board&#41;;
        for &#40;Move move &#58; jumpers&#41; &#123;
            board.makeMove&#40;move&#41;;
            int value = -quiesceneSearch&#40;board, -beta, -alpha&#41;;
            board.undoMove&#40;move&#41;;
            if&#40;value>beta&#41;
                return beta;
            if&#40;value>alpha&#41;
                alpha=value;
            &#125;
        return alpha;
    &#125;
And I got another question about Sorting the MoveList: Should I use ShellSort instead of insertionSort (which I am currently using). I tested insertionsSort vs SelectionSort and saw almost no difference. I concluded that there arent many moves that need to be swaped. When does ShellSort outperform InsertionSort ?
I did a 15second search and my algorithm spend around 25% of that time sorting the moves.
AlvaroBegue
Posts: 931
Joined: Tue Mar 09, 2010 3:46 pm
Location: New York
Full name: Álvaro Begué (RuyDos)

Re: Quiescent Search (and Sorting MoveList)

Post by AlvaroBegue »

if(value>beta)
That should be ">=".

Also, you should make some changes so your quiescence search handles being in check and checkmate correctly.

Here's my quiescence search code, in case it helps you:

Code: Select all

int Engine&#58;&#58;quiescence_search&#40;int alpha, int beta, int from_root&#41; &#123;
  ++nodes_searched;
  bool in_check = board.is_in_check&#40;);

  int stand_pat_score = 0;
  if (!in_check&#41; &#123;
    stand_pat_score = evaluate&#40;board&#41;;
    if &#40;stand_pat_score > alpha&#41; &#123;
      alpha = stand_pat_score;
      if &#40;alpha >= beta&#41;
        return alpha;
    &#125;
  &#125;

  unsigned moves&#91;256&#93;;
  int n_moves = board.generate_captures&#40;moves&#41;;
  sort_MVV_LVA&#40;board, moves, n_moves&#41;;
  if &#40;in_check&#41;
    n_moves += board.generate_noncaptures&#40;moves + n_moves&#41;;
  else
    n_moves += board.generate_noncapturing_promotions&#40;moves + n_moves&#41;;

  int legal_moves = 0;

  for &#40;int i=0; i<n_moves; ++i&#41; &#123;
    // Skip non-promising captures                                                                                     
    if (!in_check && board.SEE&#40;moves&#91;i&#93;) <= 0&#41;
      continue;

    int score = -7999 + from_root;
    board.make_move&#40;moves&#91;i&#93;);
    if (!board.is_king_exposed&#40;)) &#123;
      ++legal_moves;
      score = board.draw&#40;) ? 0 &#58; -quiescence_search&#40;-beta, -alpha, from_root + 1&#41;;
    &#125;
    board.undo_move&#40;);

    if &#40;score > alpha&#41; &#123;
      alpha = score;
      if &#40;alpha >= beta&#41;
        break;
    &#125;
  &#125;

  if &#40;in_check && legal_moves == 0&#41;
    alpha = -7999 + from_root;

  return alpha;
&#125;
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: Quiescent Search (and Sorting MoveList)

Post by Sven »

CheckersGuy wrote:Hey guys,
I have implemented QuiesceneSearch but I it didn't seem to help my engine nor did it hurt my engine in any way. I just wonder if my implementation is correct if that's not the case there is probably a bug somewhere else. (I am using failSort implementation of PVS)

Here is the implementation I am using:

Code: Select all

public static int quiesceneSearch&#40;Board board, int alpha, int beta&#41;&#123;
        
        final int standpat =board.color*GameEvaluation.evaluate&#40;board&#41;;
        if&#40;standpat>=beta&#41;
            return beta;
        if&#40;alpha<standpat&#41;
            alpha=standpat;

        ArrayList<Move> jumpers = MGenerator.getCaptures&#40;board&#41;;
        for &#40;Move move &#58; jumpers&#41; &#123;
            board.makeMove&#40;move&#41;;
            int value = -quiesceneSearch&#40;board, -beta, -alpha&#41;;
            board.undoMove&#40;move&#41;;
            if&#40;value>beta&#41;
                return beta;
            if&#40;value>alpha&#41;
                alpha=value;
            &#125;
        return alpha;
    &#125;
And I got another question about Sorting the MoveList: Should I use ShellSort instead of insertionSort (which I am currently using). I tested insertionsSort vs SelectionSort and saw almost no difference. I concluded that there arent many moves that need to be swaped. When does ShellSort outperform InsertionSort ?
I did a 15second search and my algorithm spend around 25% of that time sorting the moves.
Hi Robin,

testing for a cutoff should always be done like this:

Code: Select all

            if&#40;value>=beta&#41;
                return beta;
where you do "value > beta" instead within the move loop. The reason should be obvious (the current node will have no impact on the root if it is proven that the score returned from the current node will not improve alpha of the parent node).

Another point is that you mentioned you are using the fail-soft algorithm. In that case you should - for consistency - also do so in quiescence search, and therefore return "value" resp. "standpat" instead of "beta" in case of a cutoff. I hope you do so already in your full-width search, otherwise you are not really using fail-soft.

Sorting the move list should not take 25% of overall runtime. On the other hand, since you are in a very early stage of engine development (I derive that from the fact that you are currently adding quiescence search) I would not care too much about performance aspects at this point. Especially everything that can only change performance by a constant factor could be postponed, unless that factor is so huge and the risk of doing the change is so low that you would classify that as "low hanging fruits". In the given case I would plan to optimize that later. Nevertheless I can tell you how many engine programmers solve that: the easiest way, often also the one that performs best, is to always select the next best move when you actually need one. Since good move ordering will lead to about 80-90% cutoffs on the first move (or even more), this will basically reduce the ordering effort to O(n) since you usually go only once through the move list to find the one with the best ordering score. That move will be swapped with the move at position "i", where moves no. 1 until i-1 have already been searched and did not produce a cutoff.

I can't say why adding QS to your engine does not lead to an improvement. But typically the problems are not only in the search algorithm. There may be a problem in move ordering or even in the evaluation. You will have to debug it!
AndrewGrant
Posts: 1755
Joined: Tue Apr 19, 2016 6:08 am
Location: U.S.A
Full name: Andrew Grant

Re: Quiescent Search (and Sorting MoveList)

Post by AndrewGrant »

Two things:

One, you probably want to include promotions / enpass in your move generation for qsearch.

Two, you are going to want to sort that list of captures. Qsearch can become very problematic if you continually traverse chains of moves that are not very good. IE, you should be looking at Pawn Captures Queen first. Because this will likely cause a cutoff, where as Queen captures Pawn may not.

Also, since you are likely to not use every move located in the move list, it is advisable to iterate through the list to select each move, and then swap the selected move to the end of the list. I have an implementation of this in my engine, and I'de imagine other's look very similar.


Code: Select all

uint16_t getNextMove&#40;uint16_t * moves, int * values, int index, int size&#41;&#123;
    int i, best = 0;
    uint16_t bestMove;
    
    // FIND GREATEST VALUE
    for &#40;i = 1; i < size - index; i++)
        if &#40;values&#91;i&#93; > values&#91;best&#93;)
            best = i;
        
    bestMove = moves&#91;best&#93;;
    
    // MOVE LAST PAIR TO BEST SO WE
    // CAN REDUCE THE EFFECTIVE LIST SIZE
    moves&#91;best&#93; = moves&#91;size-index-1&#93;;
    values&#91;best&#93; = values&#91;size-index-1&#93;;
    
    return bestMove;
&#125;
#WeAreAllDraude #JusticeForDraude #RememberDraude #LeptirBigUltra
"Those who can't do, clone instead" - Eduard ( A real life friend, not this forum's Eduard )
User avatar
Luis Babboni
Posts: 464
Joined: Sat Feb 28, 2015 4:37 pm
Location: Argentina

Re: Quiescent Search (and Sorting MoveList)

Post by Luis Babboni »

CheckersGuy wrote:Hey guys,
I have implemented QuiesceneSearch but I it didn't seem to help my engine nor did it hurt my engine in any way. I just wonder if my implementation is correct if that's not the case there is probably a bug somewhere else.
...
You are not the only one with this problem right now! :D
I think I have bug and working on it but unsucssesfully till now. :(
CheckersGuy
Posts: 273
Joined: Wed Aug 24, 2016 9:49 pm

Re: Quiescent Search (and Sorting MoveList)

Post by CheckersGuy »

I think I have solved my problems with quiescentSearch now. The tips you gave me (and the example code) helped me quite a bit. However, there was something wrong with my evaluation and hence it was very unstable, which I thought was only because quiescent Search wasnt working at all.

One problem solved and now I need to solve the next one (; (Not getting the full PV because of TT-pruning is really annoying, At fixed depth I get like (depth/2) long Principal-Variation)
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: Quiescent Search (and Sorting MoveList)

Post by Sven »

CheckersGuy wrote:One problem solved and now I need to solve the next one (; (Not getting the full PV because of TT-pruning is really annoying, At fixed depth I get like (depth/2) long Principal-Variation)
Hashing the PV
flok

Re: Quiescent Search (and Sorting MoveList)

Post by flok »

AndrewGrant wrote:Two, you are going to want to sort that list of captures. Qsearch can become very problematic if you continually traverse chains of moves that are not very good. IE, you should be looking at Pawn Captures Queen first. Because this will likely cause a cutoff, where as Queen captures Pawn may not.

Also, since you are likely to not use every move located in the move list, it is advisable to iterate through the list to select each move, and then swap the selected move to the end of the list. I have an implementation of this in my engine, and I'de imagine other's look very similar.
This is not clear to me. Is this swapping method a replacement for sorting?

Code: Select all

uint16_t getNextMove&#40;uint16_t * moves, int * values, int index, int size&#41;&#123;
    int i, best = 0;
    uint16_t bestMove;
    
    // FIND GREATEST VALUE
    for &#40;i = 1; i < size - index; i++)
        if &#40;values&#91;i&#93; > values&#91;best&#93;)
            best = i;
        
    bestMove = moves&#91;best&#93;;
    
    // MOVE LAST PAIR TO BEST SO WE
    // CAN REDUCE THE EFFECTIVE LIST SIZE
    moves&#91;best&#93; = moves&#91;size-index-1&#93;;
    values&#91;best&#93; = values&#91;size-index-1&#93;;
    
    return bestMove;
&#125;
Why not keep a pointer to the last item and then proceed on to the next?
CheckersGuy
Posts: 273
Joined: Wed Aug 24, 2016 9:49 pm

Re: Quiescent Search (and Sorting MoveList)

Post by CheckersGuy »

flok wrote:
AndrewGrant wrote:Two, you are going to want to sort that list of captures. Qsearch can become very problematic if you continually traverse chains of moves that are not very good. IE, you should be looking at Pawn Captures Queen first. Because this will likely cause a cutoff, where as Queen captures Pawn may not.

Also, since you are likely to not use every move located in the move list, it is advisable to iterate through the list to select each move, and then swap the selected move to the end of the list. I have an implementation of this in my engine, and I'de imagine other's look very similar.
This is not clear to me. Is this swapping method a replacement for sorting?

Code: Select all

uint16_t getNextMove&#40;uint16_t * moves, int * values, int index, int size&#41;&#123;
    int i, best = 0;
    uint16_t bestMove;
    
    // FIND GREATEST VALUE
    for &#40;i = 1; i < size - index; i++)
        if &#40;values&#91;i&#93; > values&#91;best&#93;)
            best = i;
        
    bestMove = moves&#91;best&#93;;
    
    // MOVE LAST PAIR TO BEST SO WE
    // CAN REDUCE THE EFFECTIVE LIST SIZE
    moves&#91;best&#93; = moves&#91;size-index-1&#93;;
    values&#91;best&#93; = values&#91;size-index-1&#93;;
    
    return bestMove;
&#125;
Why not keep a pointer to the last item and then proceed on to the next?
The swapping is a replacement for sorting. The fastest sorting algorithms (for small list/Array sizes) are still O(n^2) and use quite a bit of operations.
If we assume that a cutoff happens on the first 1-2 moves quite frequently, than simply calculating the Maximum Value, which is on the order of O(n) should be faster. That`s all under the assumption that you have good moveOrdering
Thats how I understood it but please correct me If I am wrong
flok

Re: Quiescent Search (and Sorting MoveList)

Post by flok »

Andrew, how do you handle moves that need to be searched first?
Like tt hits, killers, etc.