Game Phase and tapered PSQT evaluation

Discussion of chess software programming and technical issues.

Moderator: Ras

Jon12345
Posts: 80
Joined: Tue May 11, 2010 6:18 pm

Game Phase and tapered PSQT evaluation

Post by Jon12345 »

I want to create a function to evaluate the game phase and then do a tapered evaluation between middlegame and endgame PSQTs.

I've been going through some code using the PeSTO evaluation function, but I program in JavaScript so not quite sure what is going on in some of the code. I'm hoping I can get some help here. I understand some of it, but parts I'm stuck on, so I will ask questions based on those elements.

This is the page I have been viewing: https://www.chessprogramming.org/PeSTO% ... n_Function

I think I understand that for say a pawn, you take its position on the board, lookup the mg_pawn_table and add it to the mg_value of 82. So, if the pawn was on d4, it would be 12 + 81 = 93 or 0.93 pawns in the middlegame.

But then we get to this:

Code: Select all

int gamephaseInc[12] = {0,0,1,1,1,1,2,2,4,4,0,0};
What is that exactly?

I am also a bit confused on what this does too:

Code: Select all

    /* evaluate each piece */
    for (int sq = 0; sq < 64; ++sq) {
        int pc = board[sq];
        if (pc != EMPTY) {
            mg[PCOLOR(pc)] += mg_table[pc][sq];
            eg[PCOLOR(pc)] += eg_table[pc][sq];
            gamePhase += gamephaseInc[pc];
        }
    }

    /* tapered eval */
    int mgScore = mg[side2move] - mg[OTHER(side2move)];
    int egScore = eg[side2move] - eg[OTHER(side2move)];
    int mgPhase = gamePhase;
    if (mgPhase > 24) mgPhase = 24; /* in case of early promotion */
    int egPhase = 24 - mgPhase;
    return (mgScore * mgPhase + egScore * egPhase) / 24;
Can anyone enlighten me?

Thanks.
Jon
Harald
Posts: 318
Joined: Thu Mar 09, 2006 1:07 am

Re: Game Phase and tapered PSQT evaluation

Post by Harald »

I am in the mood to explain it.

int gamephaseInc[12] = {0,0,1,1,1,1,2,2,4,4,0,0};
is just a weight table of midgame/endgame weights for each piece P,p,N,n,B,b,R,r,Q,q,K,k.
Note the sum of these weights for all 32 opening position pieces is 24.
This is not the piece score in the midgame/endgame but a measure how much
the existence of the piece indicates the 'endgamelyness' of the position.
You calculate the sum of these weights for all pieces on the board and then
24 indicates opening/midgame and 0 means late endgame. And everything in between.
The clipping of the sum to 24 is used to keep the values in that range even when after
an early promotion there are 3 queens on the board.

The calculation of mg_table[pc][sq] and eg_table[pc][sq] is just the real evaluation
of the piece material values plus the piece square table values for both colors
and for the extreme cases opening/early midgame and late endgame.
The real value is somewhere in between.

This real value or position score is then interpolated in a linear way with the line
return (mgScore * mgPhase + egScore * egPhase) / 24;
Note: mgPhase + egPhase == 24. Then you divide by 24.
This is similar to
100% mgScore + 0% egScore for an opening/midgame
...
50% mgScore + 50% egScore in the developing game
...
0% mgScore + 100% egScore for a late endgame

Other engines often just use the typical basic score weights 1, 3, 3, 5, 9
or any other piece values like 100, 310, 320, 490, 950 centipawns for the mg-eg-sum.
Then there is another divisor than 24. Just calculate your own minimum and maximum value
and modify your interpolation function. The author of Pesto obviously thought that the
score weights and mg-eg-weights should not be the same. Look at some example
piece configuration how that works and if you agree to this idea.
User avatar
mvanthoor
Posts: 1784
Joined: Wed Jul 03, 2019 4:42 pm
Location: Netherlands
Full name: Marcel Vanthoor

Re: Game Phase and tapered PSQT evaluation

Post by mvanthoor »

Jon12345 wrote: Wed Jun 23, 2021 10:32 pm Can anyone enlighten me?

Thanks.
Trying to glean a concept from a piece of code can be quite hard. With regard to your first question, I also don't know what that array means. However, tapered evaluation in itself is not difficult. In its simplest form, it works like this.

- You have a set of midgame PST's. This delivers your MG PST value.
- You have a set of endgame PST's. This delivers your EG PST value.
- You have a phase value, and from that you calculate your phase.
- You pick part of a value of the MG PST, and part of the EG PST.
- If I remember correctly, PESTO uses different piece values for the MG and EG.

What you do is this:

Create an array with these game phase values for P, N, B, R Q, K: 0, 300, 300, 500, 1000, 0

The pawns and king don't count in calculating the game phase, because you can never lose your king, and you need the pawns for the endgame.

You start by running over all the pieces on the board, and if you encounter one, you add it to a "phase_value" variable. In the starting position, you will obviously encounter 4 knights, 4 bishops, 4 rooks, and 2 queens, for a total phase_value of 6400. This is your maximum MG phase; each piece you lose moves you closer to the endgame (and thus towards the endgame table).

Let's say, you play a bit, and trade two knights (1 per side), and one bishop for 3 pawns.

Your phase_value is now 6400 - 300 (knight) - 300 (knight) - 300 (bishop) - 3 * 0 (don't count the pawns) = 5500.

The phase itself is the percentage you are still in the middle game: 5500 (current phase) / 6400 (max middle game) = 0.859.
Your endgame phase is thus: 1 - 0.859 = 0.141.

After this trade, you are 85.9 percent in the MG, and 14.1 percent in the end game. Now you interpolate / taper the evaluation, by picking 0.859 parts of the MG PST value, and 0.141 parts of the EG PST value:

white_value = (pst_w_mg * 0.895) + (pst_w_eg * 0.141)

Same for black.

That's it.

This can be enhanced in some ways, with smoother tapering, and thresholds to start the transition out of the MG later, and also arrive at full EG earlier (otherwise you would only be at full endgame after all the pieces have been traded), but the above is the basic concept. Hope this helps.

Edit: I happen to have just implemented it this evening:
https://github.com/mvanthoor/rustic/blo ... luation.rs
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL
User avatar
lithander
Posts: 924
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: Game Phase and tapered PSQT evaluation

Post by lithander »

The nice thing about the phase threshold and phase values of pieces is that you can tune them too.
And in my case with the Knight being only worth 155 phase-points and the queen over 1000 it becomes clear that this isn't just "material value".

Code: Select all

 
        const int Midgame = 5255;
        const int Endgame = 435;

        static readonly int[] PhaseValues = new int[6] { 0, 155, 305, 405, 1050, 0 };
Now with the two sets of PSTs and the phase value you can iterate over your board...

Code: Select all

   
            int midGame = 0;
            int endGame = 0;
            int phaseValue = 0;
            for (int i = 0; i < 64; i++)
            {
                Piece piece = board[i];
                if (piece == Piece.None)
                    continue;

                int sign = (int)piece.Color();
                int pieceIndex = PieceTableIndex(piece);
                int squareIndex = SquareTableIndex(i, piece);
                phaseValue += PhaseValues[pieceIndex];
                midGame += sign * MidgameTables[pieceIndex, squareIndex];
                endGame += sign * EndgameTables[pieceIndex, squareIndex];
            }
 
...and you end up with a midGame score (derived from the midGame set of PSTs) and an endGame score (derived from the endgame set of PSTs) and a phaseValue that will tell you how to mix the midGame and endGame score.

You could try to be fancy and use smoothstep interpolation but personally I just interpolate linearly.

Code: Select all

            //linearily interpolate between midGame and endGame score based on current phase (tapered eval)
            double phase = Linstep(Midgame, Endgame, phaseValue);
            double score = midGame + phase * (endGame - midGame);
            return (int)score;
For reference the Linstep code looks like this:

Code: Select all

        public static double Linstep(double edge0, double edge1, double value)
        {
            return Math.Min(1, Math.Max(0, (value - edge0) / (edge1 - edge0)));
        }
Code taken from here
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
JVMerlino
Posts: 1407
Joined: Wed Mar 08, 2006 10:15 pm
Location: San Francisco, California

Re: Game Phase and tapered PSQT evaluation

Post by JVMerlino »

This is only slightly related to the topic, but perhaps an interesting view on how GMs might think about it. GM Larry Evans once told me that, in his opinion, if both queens were off the board, you were in the endgame.
User avatar
emadsen
Posts: 441
Joined: Thu Apr 26, 2012 1:51 am
Location: Oak Park, IL, USA
Full name: Erik Madsen

Re: Game Phase and tapered PSQT evaluation

Post by emadsen »

Jon12345 wrote: Wed Jun 23, 2021 10:32 pm I want to create a function to evaluate the game phase and then do a tapered evaluation between middlegame and endgame PSQTs... But then we get to this: gamephaseInc[12] = {0,0,1,1,1,1,2,2,4,4,0,0}; What is that exactly? ... I am also a bit confused on what this does too
It's just a weighted average of the middlegame score and endgame score. AKA linearly interpolated score. AKA tapered evaluation. Score = (Mg% * MgScore) + (Eg% * EgScore).

Personally, I prefer my implementation that ensures the weights add up to a power of two. So the "percentage" calculation is done via / 256 = bitshift right 8.

Constants

Code: Select all

// Game Phase (constants selected such that starting material = 256)
public const int MiddlegamePhase = 4 * (_knightPhase + _bishopPhase + _rookPhase) + (2 * _queenPhase);
private const int _knightPhase = 10; //   4 * 10 =  40
private const int _bishopPhase = 10; // + 4 * 10 =  80
private const int _rookPhase = 22; //   + 4 * 22 = 168
private const int _queenPhase = 44; //  + 2 * 44 = 256
Game Phase

Code: Select all

private static int DetermineGamePhase(Position Position)
{
    var phase = _knightPhase * Bitwise.CountSetBits(Position.WhiteKnights | Position.BlackKnights) +
                _bishopPhase * Bitwise.CountSetBits(Position.WhiteBishops | Position.BlackBishops) +
                _rookPhase * Bitwise.CountSetBits(Position.WhiteRooks | Position.BlackRooks) +
                _queenPhase * Bitwise.CountSetBits(Position.WhiteQueens | Position.BlackQueens);
    return Math.Min(phase, MiddlegamePhase);
}
Tapered Eval

Code: Select all

private int GetTaperedScore(int Phase) => GetTaperedScore(WhiteMg - BlackMg, WhiteEgScaled - BlackEgScaled, Phase);


// Linearly interpolate between middlegame and endgame scores.
public static int GetTaperedScore(int MiddlegameScore, int EndgameScore, int Phase) =>
((MiddlegameScore * Phase) + (EndgameScore * (Evaluation.MiddlegamePhase - Phase))) / Evaluation.MiddlegamePhase;
This corresponds closely to Fruit's 1, 1, 2, 4 weights. So, a trade of queens tips the eval balance to the endgame values twice as much as a trade of rooks, and four times as much as a trade of minor pieces.
Erik Madsen | My C# chess engine: https://www.madchess.net
User avatar
hgm
Posts: 28435
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Game Phase and tapered PSQT evaluation

Post by hgm »

I thought Fruit used 0,1,1,3,6, to make them add up to a power of 2 (namely 32), so that the division by the total is just a right-shift.

To save the overhead of keeping track of separate MG and EG evaluations, I often combine these into a single value, like mixEval = (MG - EG)*N + EG, with N something like 1<<16 (=64K), and offset the incerementally kept gamePhase by N (so that it is really N+phase). Then I only have to multiply

gamePhase * mixEval = (N + phase)*((MG-EG)*N + EG) = N*N *(MG-EG) + N*phase*(MG-EG) + N*EG + phase*EG = N*(phase*(MG-EG) + EG) + phase*EG

because the N*N part is clipped off the product by overflow (N*N = 1<<32, and we only have 32 bit integers). Then the result is divided by N*MAXPHASE (e.g. >> 20), which gets rid of the phase*EG (which is negligible compared to 64K), and leaves us with

(phase*(MG-EG) + EG)/MAXPHASE = phase/MAXPHASE*MG + (1 - phase/MAXPHASE)*EG

as desired.

All the PST and parameters for the Pawn-structure evaluation are then 32-bit integers stored in this mixEval format. (I use a preprocessor macro MIX(MG,EG) for specifying their values.) The only difference between a tapered and non-tapered eval is then that the incremental eval is multiplied by the (incrementaly updated) gamephase, and shifted right a number of bits.
op12no2
Posts: 555
Joined: Tue Feb 04, 2014 12:25 pm
Location: Gower, Wales
Full name: Colin Jenkins

Re: Game Phase and tapered PSQT evaluation

Post by op12no2 »

Jon12345 wrote: Wed Jun 23, 2021 10:32 pm ... but I program in JavaScript ...
Lozza 3 (Javascript) has a very simple tapered eval that may or may not help:-

https://github.com/op12no2/lozza/blob/m ... a.js#L4117

phase is tracked in make/unmakeMove and then sanitised in eval.
User avatar
hgm
Posts: 28435
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Game Phase and tapered PSQT evaluation

Post by hgm »

With 'sanitized' you mean clipped to the value for the opening position? I don't understand why anyone would want to do that. If you interpolate, it is logical to extrapolate as well. It shouldn't have any effect on Elo anyway; the number of games in which material would exceed the opening material will be completely negligible.
op12no2
Posts: 555
Joined: Tue Feb 04, 2014 12:25 pm
Location: Gower, Wales
Full name: Colin Jenkins

Re: Game Phase and tapered PSQT evaluation

Post by op12no2 »

hgm wrote: Thu Jun 24, 2021 11:26 am With 'sanitized' you mean clipped to the value for the opening position? I don't understand why anyone would want to do that. If you interpolate, it is logical to extrapolate as well. It shouldn't have any effect on Elo anyway; the number of games in which material would exceed the opening material will be completely negligible.
Yes exactly that, the comment says "// because of say 3 queens early on.". But point taken on extrapolation. As I recall I was testing something and the phase was confusing the heck out of me so, I 'sanitised it'. Can't remember if I tested it.