Confused About Triangular PV Table

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

shahil4242
Posts: 30
Joined: Thu Sep 28, 2017 5:20 pm

Confused About Triangular PV Table

Post by shahil4242 »

I had studied the source of Lime,TSCP I am trying to implement Triangular PV for my chess engine

In the beginning of tscp there is a line

Code: Select all

pv_length[ply] = ply
When there best move is found it updates like in way

Code: Select all

pv_length[ply] = pv_length[ply+1]
In the beginning of search function these values are reset to 0.

Note index 0 serves as the length of the pv table at certain depth meaning the length of pv table at depth 3 will stored at index 0 of pv_length.But my implementation gives output at index 0 is 0 and so on..

Have a great day ahead.
User avatar
lithander
Posts: 881
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: Confused About Triangular PV Table

Post by lithander »

I'm not sure what exactly the question is but maybe it helps to show you my PV table implementation:

Code: Select all

class PrincipalVariation
{
	Move[] _moves = new Move[0];
	int _depth = 0;

	private int Index(int depth)
	{
		//return depth + (depth - 1) + (depth - 2) + ... + 1;
		int d = depth - 1;
		return (d * d + d) / 2;
	}

	public void Grow(int depth)
	{
		while (_depth < depth)
			Grow();
	}

	private void Grow()
	{
		_depth++;
		int size = Index(_depth + 1);
		Move[] moves = new Move[size];
		//copy pv lines to new array
		int to = 0;
		for (int depth = 0; depth < _depth; depth++)
		{
			//copy the last d elements of the mainline 
			for (int i = 0; i < depth; i++)
				moves[to++] = _moves[^(depth - i)];
			//leave one free
			to++;
		}
		_moves = moves;
	}

	public Move[] GetLine(int depth)
	{
		int start = Index(depth);
		int nullMove = Array.IndexOf(_moves, default, start, depth);
		int count = (nullMove == -1) ? depth : nullMove - start;

		Move[] line = new Move[count];
		Array.Copy(_moves, start, line, 0, count);
		return line;
	}

	public Move this[int depth]
	{
		get
		{
			return _moves[Index(depth)];
		}
		set
		{
			int a = Index(depth);
			_moves[a] = value;
			//remember the continuation
			int b = Index(depth - 1);
			for (int i = 0; i < depth - 1; i++)
				_moves[a + i + 1] = _moves[b + i];
		}
	}
}
In the iterative search when you want to search deeper you call Grow(Depth) and the _moves array will get resized, the moves stored in there get copied to their new places and you'll make room for a new PV at depth 'Depth' which of course has the length 'Depth'. This is what makes the table look conceptually like a triangle even though it's implemented as a one-dimensional array.

You can read the best move from the PV datastructure using the indexer.

Code: Select all

_pv[depth] = newBestMove;
storedBestMove = _pv[depth];
And depth 0 is really a leaf node. When you grow the PV-table to eg 10 then _pv[10] is where the best root move is stored.
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
shahil4242
Posts: 30
Joined: Thu Sep 28, 2017 5:20 pm

Re: Confused About Triangular PV Table

Post by shahil4242 »

Thank you.Now i got it.
User avatar
hgm
Posts: 27837
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Confused About Triangular PV Table

Post by hgm »

I usually implement the PV collection as a (1-dimensional) stack of PVs/moves. Rather than explicitly storing the length of each PV I just terminate each PV by an invalid move code (usually 0, as moves are encoded as integers). Each node then starts initializing an empty PV at the top of that stack, remembering where it starts, so it can 'pop' its own PV from the stack by resetting the stack pointer to the original value when it returns. (Without actually destroying the data, so that the parent can pick it up when it needs it.)

Code: Select all

// on entry
int myPV = pvPtr;
pv[pvPtr++] = INVALID; // initialize empty PV

// on overturning PV move
int childPV = pvPtr; // this is where the child's PV still resides
pvPtr = myPV;        // pop old PV of this node
pv[pvPtr++] = move;  // first move
while((pv[pvPtr++] = pv[childPV++) != INVALID) {} // copy child PV behind it

// on exit
pvPtr = myPV; // pop PV (leaving data)
User avatar
j.t.
Posts: 239
Joined: Wed Jun 16, 2021 2:08 am
Location: Berlin
Full name: Jost Triller

Re: Confused About Triangular PV Table

Post by j.t. »

Check how many PV nodes your engine usually visits. Often there are only a few thousand PV nodes. It may be easier to just store all PV nodes in a hash table (for example, std::unordered_hash for C++). Even though such a hash table is inefficient with memory usage, it doesn't really matter, as you only need to store a few thousand elements anyway.
pedrojdm2021
Posts: 157
Joined: Fri Apr 30, 2021 7:19 am
Full name: Pedro Duran

Re: Confused About Triangular PV Table

Post by pedrojdm2021 »

I never understood very good how to store Principal variation in a correct way :oops: in my chess application i do not calculate PV, i only set the "best"
move by:

Code: Select all

table[ply] = current_move;
and i get it from:

Code: Select all

best_move = table[0];
i wish that there would be an simple to understand approarch and also that works just fine
User avatar
mvanthoor
Posts: 1784
Joined: Wed Jul 03, 2019 4:42 pm
Location: Netherlands
Full name: Marcel Vanthoor

Re: Confused About Triangular PV Table

Post by mvanthoor »

pedrojdm2021 wrote: Wed Jun 23, 2021 12:50 am i wish that there would be an simple to understand approarch and also that works just fine
A principal variation is the best move in a node, with all the best moves of the nodes below it appended after it. I store the PV like this:

- Give the Alpha-beta function a "pv" argument variable, which is a reference to a vector of moves.
- In Iterative Deepening, create a variable called "root_pv", which a vector of moves. Send a reference to this variable into alpha-beta, in the "pv" spot. The "root_pv" variable will hold the PV for the search.
- In the Alpha-Beta function, create a "node_pv" variable to store the pv for the node you are in. For each recursive call of alpha-beta, you send a reference to _that_ variable ("node_pv") into alpha-beta, in the "pv" argument. This will build the PV for the node you are in.
- Then, when Alpha improves, you do exactly as I described: the PV is the best move, with the PV of the node appended behind it:

Code: Select all

// We found a better move for us.
if eval_score > alpha {
	//... (some code)
	
	// Update the Principal Variation.
	pv.clear();
	pv.push(current_move);
	pv.append(&mut node_pv);
}
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL
User avatar
hgm
Posts: 27837
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Confused About Triangular PV Table

Post by hgm »

pedrojdm2021 wrote: Wed Jun 23, 2021 12:50 am I never understood very good how to store Principal variation in a correct way :oops: in my chess application i do not calculate PV, i only set the "best"
move by:

Code: Select all

table[ply] = current_move;
and i get it from:

Code: Select all

best_move = table[0];
i wish that there would be an simple to understand approarch and also that works just fine
The problem with what you do is that a tree has many branches. And what you store for a certain ply is the best move in the node on the last branch you searched at a certain ply. Which is not necessarilythe best branch. In fact one usually takes great effort to make sure the best branch is searched first.

So you cannot just alter the later moves in a PV that you obtained before in any node where you find a better move. You must first verify that the branch the node is on has a better score than the PV you already had. And you only can know that after the search on that sub-tree has finished. So you need some temporary storage for storing the sequence of best moves of the branch you are working on, so that you can save them without destroying the PV you already had. And when it finally turns out that the latest branch was better, (i.e. score between alpha and beta) you overwrite your previous PV with the temporary one, by copying the latter in its entirety. But if it is not better, you just discard it, and keep the old.

You have to do that for side branches that start at every ply. So each node needs a storage for its own tentative PV, which is the PV for the best move it found so far. This is what the 'triangular array' method does; each row is a PV, and each ply level has its own row as temporary storage for its tentative PV.
pedrojdm2021
Posts: 157
Joined: Fri Apr 30, 2021 7:19 am
Full name: Pedro Duran

Re: Confused About Triangular PV Table

Post by pedrojdm2021 »

hgm wrote: Wed Jun 23, 2021 12:53 pm
pedrojdm2021 wrote: Wed Jun 23, 2021 12:50 am I never understood very good how to store Principal variation in a correct way :oops: in my chess application i do not calculate PV, i only set the "best"
move by:

Code: Select all

table[ply] = current_move;
and i get it from:

Code: Select all

best_move = table[0];
i wish that there would be an simple to understand approarch and also that works just fine
The problem with what you do is that a tree has many branches. And what you store for a certain ply is the best move in the node on the last branch you searched at a certain ply. Which is not necessarilythe best branch. In fact one usually takes great effort to make sure the best branch is searched first.

So you cannot just alter the later moves in a PV that you obtained before in any node where you find a better move. You must first verify that the branch the node is on has a better score than the PV you already had. And you only can know that after the search on that sub-tree has finished. So you need some temporary storage for storing the sequence of best moves of the branch you are working on, so that you can save them without destroying the PV you already had. And when it finally turns out that the latest branch was better, (i.e. score between alpha and beta) you overwrite your previous PV with the temporary one, by copying the latter in its entirety. But if it is not better, you just discard it, and keep the old.

You have to do that for side branches that start at every ply. So each node needs a storage for its own tentative PV, which is the PV for the best move it found so far. This is what the 'triangular array' method does; each row is a PV, and each ply level has its own row as temporary storage for its tentative PV.
it sounds like rocket science to me, i have to study it deeply :)
User avatar
hgm
Posts: 27837
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Confused About Triangular PV Table

Post by hgm »

It should not be, when you keep in mind that each node has its own complete PV, independent of other nodes at the same ply level. E.g. the PV for 1.d4 is completely different from the PV for 1.f4, in the start position.

And, just like scores, PVs propagate to towards the root. According to the rule that a node's PV is the PV of its best child, prefixed by the best move (which leads to that child).