Difference between revisions of "Triangular PV-Table"

From Chessprogramming wiki
Jump to: navigation, search
 
Line 123: Line 123:
 
=External Links=  
 
=External Links=  
 
* [http://web.archive.org/web/20040427013839/brucemo.com/compchess/programming/pv.htm Collecting the Principal Variation] from [[Bruce Moreland|Bruce Moreland's]] [http://web.archive.org/web/20040403211728/brucemo.com/compchess/programming/index.htm Programming Topics]
 
* [http://web.archive.org/web/20040427013839/brucemo.com/compchess/programming/pv.htm Collecting the Principal Variation] from [[Bruce Moreland|Bruce Moreland's]] [http://web.archive.org/web/20040403211728/brucemo.com/compchess/programming/index.htm Programming Topics]
 +
* [[:Category:Indigo Jam Unit|Indigo Jam Unit]] - 2×2 PV, 2010, [https://en.wikipedia.org/wiki/YouTube YouTube] Video
 +
: {{#evu:https://www.youtube.com/watch?v=DAGCKM7Cgng|alignment=left|valignment=top}}
  
 
=References=  
 
=References=  
 
<references />
 
<references />
 
 
'''[[Data|Up one Level]]'''
 
'''[[Data|Up one Level]]'''
 +
[[Category:Indigo Jam Unit]]

Latest revision as of 23:12, 3 December 2019

Home * Programming * Data * Triangular PV-Table

Triangular Table [1]

A Triangular PV-Table is an array of principal variations indexed by ply (distance to root). It is employed to collect the principal variation of best moves inside an alpha-beta or principal variation search - like the best score propagated up to the root. Inside an iterative deepening framework, it is most important to play the principal variation first during the next iteration. With some care in not overwriting PV-nodes, many programmers have abandoned PV-Table and reveal the PV from the transposition table [2] [3] .

Layout

Assuming a maximum search depth of N plies with pre-allocated stacks, the maximum possible PV-length decreases with increasing distance to root aka ply index during search, and actually needs one move less each ply deeper.

maxLengthPV(ply) = N - ply

Therefor the triangular structure.

ply  maxLengthPV
    +--------------------------------------------+
0   |N                                           |
    +------------------------------------------+-+
1   |N-1                                       |
    +----------------------------------------+-+
2   |N-2                                     |
    +--------------------------------------+-+
3   |N-3                                   |
    +------------------------------------+-+
4   |N-4                                 |
...                        /
N-4 |4      |
    +-----+-+
N-3 |3    |
    +---+-+
N-2 |2  |
    +-+-+
N-1 |1|
    +-+

Implementations

One-Dimensional Array

Size

The total size of the triangular array in moves can be calculated by the Triangular number:

size = 1+2+3+ ... +(N-1)+N = ½ N(N+1)

Index

To calculate the index or offset of a PV into a one-dimensional move array by ply either requires storing incremental offsets

index(0) = 0 
index(ply+1) = index(ply) + N - ply 

or variable multiplication from scratch:

index(ply) = ½ ply (2N + 1 - ply )

This index calculation might as well be replaced by a redirection via a small pre-calculated lookup table with N entries of indices, pointers or references, similar to two-dimensional Java arrays as arrays of arrays with different size [4] .

Pseudo Code

A didactic implementation of the Triangular PV-Table inside an Alpha-Beta routine. The routine movcpy copies up to n moves, but may terminate after copying a null move. To avoid the second condition, it is quite common to store the current length of the PV inside a PV structure.

MoveType pvArray[(N*N + N)/2];

void movcpy (MoveType* pTarget, const MoveType* pSource, int n) {
   while (n-- && (*pTarget++ = *pSource++));
}

int AlphaBeta(int alpha, int beta, int depth, int ply, int pvIndex) {
   ...
   pvArray[pvIndex] = 0; // no pv yet
   pvNextIndex = pvIndex + N - ply;
   while ( move = getNextMove() ) {
      make (move);
      score = -AlphaBeta(-beta, -alpha, depth-1, ply+1, pvNextIndex);
      unmake (move);
      ...
      if (score > alpha) {
         alpha = score;
         pvArray[pvIndex] = move;
         movcpy (pvArray + pvIndex + 1, pvArray + pvNextIndex, N - ply - 1);
      }
   }
   ...

Quadratic PV-Table

To avoid the above index calculation, many programmers roughly double the table space for an homogeneous two-dimensional array with all PVs of maximum size N. This allows cheaper indexing due the multiplication by the constant N only, possibly replaced by cheap instructions, i.e. shift for N == 128 (power of two).

Linked List on the Stack

An option in C as well in Java is to reserve space for one PV on the stack as automatic variable inside the recursive search routine, and to pass a reference or pointer to the child nodes as demonstrated by Bruce Moreland [5] . Inside PVS, these arrays are not needed in the zero window search at all, and, C99 conform variable-length arrays on the stack are the way to "half" the required stacksize for linked "triangular" PV-arrays.

Pointer Array

Another alternative is an array of N pointers to global, homogeneous PV-arrays with size N each. Instead of copying moves alá memcpy, one may swap pointers. While this sounds promising at the first glance, the copy costs are negligible since PV-Nodes and raising alpha are quite rare.

PV in PVS

As demonstrated by Daniel Shawul with TSCP [6] , and explained by Harm Geert Muller [7] , inside a perfectly stable NegaScout aka Principal Variation Search, a fail-high at a PV-node compared to a null window is always confirmed by the re-search with an exact score improving alpha and will either become part of a new PV at the root or overwritten by a further improvement. Thus, under such conditions, there is no need to keep an array of principal variations, but only one.

MoveType pvArray[N];

However, with a transposition table, aspiration, selectivity and that like, one cannot guarantee such stable behavior.

See also

Forum Posts

1998 ...

2000 ...

2005 ...

2010 ...

2015 ...

External Links

References

Up one Level