New engine: Peacekeeper

Discussion of chess software programming and technical issues.

Moderator: Ras

Sazgr
Posts: 66
Joined: Thu Dec 09, 2021 8:26 pm
Full name: Kyle Zhang

Re: New engine: Peacekeeper

Post by Sazgr »

Aldus wrote: Wed Jan 18, 2023 4:25 pm If I was writing a chess engine, I would probably consider implementing first a correct (even if slow) detection of mobility and then running fixed depth games against the older version, just to see if the new eval is any better. If this is not the case, it doesn't even worth trying to optimise it.

As to the mobility itself, we may already evaluate it without even knowing. For instance, one might argue that the Piece-Square Tables (PST) values are an approximation of mobility considering an empty board. For why does a knight on E4 have a greater PST value than a knight on A1 other that on E4 the knight has 8 moves versus only 2 on A1? This leads us to the following 2 important points:

1. Maybe mobility should not be evaluated in a simplistic and linear way (the optimum PST values for the knight on E4 and A1 are probably not 8 and 2).

2. Mobility overlaps somehow with PST.

Especially due to the last point, if we have a carefully tuned (either by hand/intuition or with Texel etc.) PST value for the knight on E4 and we suddenly and blindly add it's mobility count, we may destabilise the evaluation.

I guess mobility is both expensive to calculate and tricky to get it right. On the other hand, we may just get it left by randomly trying various ideas (as suggested here) until finding one that rises the ELO with a few points. :)
I've been attempting to tune mobility with the piece square tables together. I am trying to use a square root formula for mobility, so that controlling an extra square with a given piece is less valuable the more mobile that piece already is. But yes, the knight mobility table really is like a PST, unless some types of moves are counted less or more.

I have run fixed node games, which do show that mobility would gain around 30-40 elo, but that gain dissipates when time control is used instead. I was trying to get a larger gain so that it wouldn't all go away in time controls.
Sazgr
Posts: 66
Joined: Thu Dec 09, 2021 8:26 pm
Full name: Kyle Zhang

Re: New engine: Peacekeeper

Post by Sazgr »

I was looking at some CCRL games, and it seems there may be a bug that causes very stupid blunders on my engine's part. :cry:

[pgn]
[Event "CCRL Blitz"]
[Site "CCRL"]
[Date "2023.01.14"]
[Round "673.4.725"]
[White "Peacekeeper 1.10 64-bit"]
[Black "Arion 1.7"]
[Result "0-1"]
[ECO "B07"]
[Opening "Pirc defence"]
[PlyCount "56"]
[WhiteElo "2426"]
[BlackElo "2333"]

1. e4 d6 2. d4 Nf6 3. Nc3 g6 4. Be3 Bg7 5. Qd2 c6 6. Nf3 O-O 7. h3 Qa5 8. Bd3
e5 9. dxe5 dxe5 10. O-O Be6 11. Ng5 Re8 12. Nxe6 Rxe6 13. a3 Rd6 14. Rad1 Nbd7
15. b4 Qc7 16. Bh6 Bxh6 17. Qxh6 a5 18. Ne2 b5 19. Rfe1 Qb6 20. Rb1 axb4 21.
axb4 Ra4 22. g4 Ne8 23. g5 Nc7 24. Nc3 Ra3 25. Qg7+ Kxg7 26. Ne2 c5 27. bxc5
Nxc5 28. Nc1 f6 0-1
[/pgn]

[pgn]
[Event "CCRL Blitz"]
[Site "CCRL"]
[Date "2023.01.13"]
[Round "673.4.649"]
[White "Absolute Zero 3.3.2.0 64-bit"]
[Black "Peacekeeper 1.10 64-bit"]
[Result "1-0"]
[ECO "A55"]
[Opening "Old Indian"]
[Variation "main line"]
[PlyCount "90"]
[WhiteElo "2341"]
[BlackElo "2426"]

1. d4 Nf6 2. c4 d6 3. Nc3 Nbd7 4. e4 e5 5. Nf3 c6 6. Be2 Be7 7. O-O O-O 8. a3
exd4 9. Nxd4 Ne5 10. f4 Ng6 11. g4 h6 12. Nf5 Bxf5 13. gxf5 Nh4 14. Kh1 Re8 15.
e5 dxe5 16. fxe5 Nd7 17. e6 fxe6 18. fxe6 Ne5 19. Bf4 Bf6 20. Qb3 Nhg6 21. Ne4
Nxf4 22. Rxf4 Bg5 23. Rf5 Rxe6 24. Nxg5 hxg5 25. Qxb7 Rb8 26. Qxa7 Rxb2 27. Rd1
Qe8 28. Rf2 Rb3 29. Qa4 Re3 30. c5 Qg6 31. Rg2 Nf7 32. Ba6 R6e4 33. Qc2 Qe6 34.
Bd3 Rd4 35. Bh7+ Kh8 36. Rxd4 Re1+ 37. Rg1 Rxg1+ 38. Kxg1 Qe3+ 39. Qf2 Qxf2+
40. Kxf2 Kxh7 41. a4 g4 42. a5 Ng5 43. a6 Ne6 44. Rd1 Nxc5 45. a7 Ne4+ 1-0
[/pgn]
User avatar
lithander
Posts: 915
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: New engine: Peacekeeper

Post by lithander »

Aldus wrote: Wed Jan 18, 2023 4:25 pm I guess mobility is both expensive to calculate and tricky to get it right. On the other hand, we may just get it left by randomly trying various ideas (as suggested here) until finding one that rises the ELO with a few points
Mobility can be a pretty big source of Elo for an engine that is just using PSTs otherwise so it's really worth to explore how to harness that potential when you have just tapered PSTs.

I don't know how others come up with their handcrafted evaluations but in my engine Leorik I focus mostly on the features that I want the eval to be aware of and then rely heavily on my tuning code to find the right values for me. It's all just one large linear function! A (fun!) challenge is to figure out how to extract the features of a position at runtime fast enough so that the performance cost loses less Elo than what gain from the improved eval accuracy.

For mobility, if you look at each piece-type in isolation there is a max number of moves a piece can make. Even a queen has only 28 different states in terms of mobility. So I don't need a formula, I can just use a lookup table. From the tuner's point finding the mobility coefficients and the material coefficients (e.g. PST value) is just one and the same thing. So I don't "suddenly and blindly add it's mobility count" because every value was tuned together with linear regression.

Code: Select all

        //Max possible moves:
        //Pawn      = 12 + zero = 13  [00..12]
        //Knight    =  8 + zero =  9  [13..21]
        //Bishop    = 13 + zero = 14  [22..35]
        //Rook      = 14 + zero = 15  [36..50]
        //Queen     = 27 + zero = 28  [51..78]
        //King      =  8 + zero =  9  [79..87]
        //--------------
        //TOTAL     = 82        = 88
        //static short[] PieceMobilityIndices = new short[8] { 0, 0, 13, 22, 36, 51, 79, 88 };

        public static (short, short)[] Mobility = new (short, short)[88]
        {
            //Pawn:
            (-8,-19), (0,0), (0,0), (0,0), (99,147), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
            //Knight:
            (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
            //Bishop:
            (0,0), (-2,48), (12,65), (12,81), (18,104), (21,117), (26,115), (27,119), (32,119), (41,110), (44,99), (49,92), (42,79), (46,85),
            //Rook:
            (0,0), (8,-1), (13,27), (19,29), (24,19), (32,13), (37,13), (48,1), (55,1), (60,1), (66,1), (73,-3), (86,-12), (96,-37), (94,-30),
            //Queen:
            (0,0), (9,-8), (10,3), (9,2), (11,-23), (9,34), (5,62), (7,60), (5,82), (11,72), (11,87), (14,86), (12,112), (15,105), (29,67), (16,112), (15,121), (6,139), (16,126), (23,95), (40,89), (31,93), (42,77), (39,81), (16,65), (2,57), (-58,22), (-61,21),
            //King:
            (0,0), (9,37), (16,6), (17,5), (3,15), (6,-12), (-8,8), (-39,27), (-32,-23),
        };
This is my mobility data in Leorik. The two numbers in brackets together allow you to have the mobility value change linearly with game phase.
So the penalty for a pawn that can't move is bonus(phase) = -8 + phase * -19

As you can see the Knight has no mobility component in my table. PST values alone were sufficient for him! :)
Last edited by lithander on Wed Jan 18, 2023 5:53 pm, edited 2 times in total.
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
User avatar
lithander
Posts: 915
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: Mobility evaluation question

Post by lithander »

hgm wrote: Tue Jan 17, 2023 10:06 pm
lithander wrote: Sat Jan 14, 2023 6:28 pmWhen I go into QSearch I stop updating the mobility term of the evaluation.
A compromise is to incorporate the mobility in the piece values before entering QS ('static mobility'), and update your material term during QS on a per-piece basis rather than per type. This would prevent that you trade, say, a very active Knight of yourself for a trapped Knight of the opponent.

Mobility is an expensive evaluation term. To speed it up it can be calculated incrementally, but that is not really trivial.
This could work. But I'm also using SEE to skip "bad" captures wich does not consider square-specific values for pieces let alone mobility. I doubt that I can make that aware of static mobility values and keep it at useful speed.

...another idea I had to be a little more accurate is that when QSearch concludes you could not only return a score but also the associated leaf node (or the PV that leads to that position) and use that to update the mobility. It wouldn't prevent qsearch from finding a suboptimal exchange, just accurately reflect the result of that exchange.

But nothing other than really updating the mobility term after each move would be totally accurate. Not even your solution as far as I can see.
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
Sazgr
Posts: 66
Joined: Thu Dec 09, 2021 8:26 pm
Full name: Kyle Zhang

Re: New engine: Peacekeeper

Post by Sazgr »

lithander wrote: Wed Jan 18, 2023 5:33 pm
Aldus wrote: Wed Jan 18, 2023 4:25 pm I guess mobility is both expensive to calculate and tricky to get it right. On the other hand, we may just get it left by randomly trying various ideas (as suggested here) until finding one that rises the ELO with a few points
Mobility can be a pretty big source of Elo for an engine that is just using PSTs otherwise so it's really worth to explore how to harness that potential when you have just tapered PSTs.

I don't know how others come up with their handcrafted evaluations but in my engine Leorik I focus mostly on the features that I want the eval to be aware of and then rely heavily on my tuning code to find the right values for me. It's all just one large linear function! A (fun!) challenge is to figure out how to extract the features of a position at runtime fast enough so that the performance cost is costing more Elo than what gain fromt he improved eval accuracy.

For mobility, if you look at each piece-type in isolation there is a max number of moves a piece can make. Even a queen has only 28 different states in terms of mobility. So I don't need a formula, I can just use a lookup table. Just like PSTs. From the tuner's point finding the mobility coefficients and the material coefficients (PSTs) is just one and the same thing. So I don't "suddenly and blindly add it's mobility count" because every value was tuned together with linear regression.

Code: Select all

        //Max possible moves:
        //Pawn      = 12 + zero = 13  [00..12]
        //Knight    =  8 + zero =  9  [13..21]
        //Bishop    = 13 + zero = 14  [22..35]
        //Rook      = 14 + zero = 15  [36..50]
        //Queen     = 27 + zero = 28  [51..78]
        //King      =  8 + zero =  9  [79..87]
        //--------------
        //TOTAL     = 82        = 88
        //static short[] PieceMobilityIndices = new short[8] { 0, 0, 13, 22, 36, 51, 79, 88 };

        public static (short, short)[] Mobility = new (short, short)[88]
        {
            //Pawn:
            (-8,-19), (0,0), (0,0), (0,0), (99,147), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
            //Knight:
            (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0), (0,0),
            //Bishop:
            (0,0), (-2,48), (12,65), (12,81), (18,104), (21,117), (26,115), (27,119), (32,119), (41,110), (44,99), (49,92), (42,79), (46,85),
            //Rook:
            (0,0), (8,-1), (13,27), (19,29), (24,19), (32,13), (37,13), (48,1), (55,1), (60,1), (66,1), (73,-3), (86,-12), (96,-37), (94,-30),
            //Queen:
            (0,0), (9,-8), (10,3), (9,2), (11,-23), (9,34), (5,62), (7,60), (5,82), (11,72), (11,87), (14,86), (12,112), (15,105), (29,67), (16,112), (15,121), (6,139), (16,126), (23,95), (40,89), (31,93), (42,77), (39,81), (16,65), (2,57), (-58,22), (-61,21),
            //King:
            (0,0), (9,37), (16,6), (17,5), (3,15), (6,-12), (-8,8), (-39,27), (-32,-23),
        };
This is my mobility data in Leorik. The two numbers in brackets together allow you to have the mobility value change linearly with game phase.
So the penalty for a pawn that can't move is bonus(phase) = -8 + phase * -19

As you can see the Knight has no mobility component in my table. PST values alone were sufficient for him! :)
So when the pawn has 4 moves, it is given a large bonus, and never else? Is that when it could promote? Some of the phase based mobility values of the bishop or the queen are very large if the phase value is big. What is the maximum phase value of Leorik? Since the knight isn't a slider, I think it always has almost the same number of moves at any constant square, which will be rolled into the PSTs anyway. And if you are excluding, say, squares with your own pieces from its mobility, then it is penalized for protecting pieces, which might be an improvement.
User avatar
lithander
Posts: 915
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: New engine: Peacekeeper

Post by lithander »

Sazgr wrote: Wed Jan 18, 2023 5:56 pm So when the pawn has 4 moves, it is given a large bonus, and never else? Is that when it could promote?
Yes!
Sazgr wrote: Wed Jan 18, 2023 5:56 pm Some of the phase based mobility values of the bishop or the queen are very large if the phase value is big. What is the maximum phase value of Leorik?
Phase is always between 0 and 1. Mobility and PSTs can both encode material value. You could subtract 100cp from all values in a piece's mobility table and add it to the pieces PST and nothing changes. So you could also read it as: "a stuck bishop is really bad"...

I thought it would be probably best if the "default-moblity" would be assigned (0, 0) it's hard to define what the default mobility of a piece is supposed to be. Tried a few rebalancing schemes but no big difference compared to the current one where (0,0) is always assigned to zero moves. Maybe I'll revisit it later.
Sazgr wrote: Wed Jan 18, 2023 5:56 pm Since the knight isn't a slider, I think it always has almost the same number of moves at any constant square, which will be rolled into the PSTs anyway. And if you are excluding, say, squares with your own pieces from its mobility, then it is penalized for protecting pieces, which might be an improvement.
Is it bad to protect pieces with a knight? I don't understand chess well enough. But squares with my own pieces are always excluded from my mobility values because a piece can't go to an occupied square. I thought that's the entire point of mobility?
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
User avatar
hgm
Posts: 28353
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: New engine: Peacekeeper

Post by hgm »

I guess the point is more to not trap your own sliders, but put them on squares where they have long free paths.

My intuition says that it is bad to leave as many pieces unprotected as possible. In end-games with Queens you usually lose such pieces ithout compensation.

One could also argue that mobility is a poor man's approximation to board control. So that extra moves to squares that are already covered by other pieces (especially lower-valued pieces) aren't worth much.
Sazgr
Posts: 66
Joined: Thu Dec 09, 2021 8:26 pm
Full name: Kyle Zhang

Re: New engine: Peacekeeper

Post by Sazgr »

lithander wrote: Wed Jan 18, 2023 6:50 pm
Sazgr wrote: Wed Jan 18, 2023 5:56 pm So when the pawn has 4 moves, it is given a large bonus, and never else? Is that when it could promote?
Yes!
Sazgr wrote: Wed Jan 18, 2023 5:56 pm Some of the phase based mobility values of the bishop or the queen are very large if the phase value is big. What is the maximum phase value of Leorik?
Phase is always between 0 and 1. Mobility and PSTs can both encode material value. You could subtract 100cp from all values in a piece's mobility table and add it to the pieces PST and nothing changes. So you could also read it as: "a stuck bishop is really bad"...

I thought it would be probably best if the "default-moblity" would be assigned (0, 0) it's hard to define what the default mobility of a piece is supposed to be. Tried a few rebalancing schemes but no big difference compared to the current one where (0,0) is always assigned to zero moves. Maybe I'll revisit it later.
I see. My phase goes from 0 to 24, so I saw the +100cps and thought somehow that a bishop was +2500cp :oops:
Sazgr wrote: Wed Jan 18, 2023 5:56 pm Since the knight isn't a slider, I think it always has almost the same number of moves at any constant square, which will be rolled into the PSTs anyway. And if you are excluding, say, squares with your own pieces from its mobility, then it is penalized for protecting pieces, which might be an improvement.
Is it bad to protect pieces with a knight? I don't understand chess well enough. But squares with my own pieces are always excluded from my mobility values because a piece can't go to an occupied square. I thought that's the entire point of mobility?
hgm wrote: Thu Jan 19, 2023 7:27 am I guess the point is more to not trap your own sliders, but put them on squares where they have long free paths.

My intuition says that it is bad to leave as many pieces unprotected as possible. In end-games with Queens you usually lose such pieces ithout compensation.

One could also argue that mobility is a poor man's approximation to board control. So that extra moves to squares that are already covered by other pieces (especially lower-valued pieces) aren't worth much.
Sorry if I'm being very confusing (I probably am). What I was meaning is that if knight mobility bonuses are applied for only empty squares and squares occupied by opponent, then it is equivalent to applying penalties for protecting friendly pieces, which may not be worse. That may be why lithander's knight mobility is all 0's. Maybe it might be best to only discount squares occupied by pawns in knight mobility, or mobility in general, because they move out of the way less often and having high value pieces protect them is worth less.
User avatar
hgm
Posts: 28353
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: New engine: Peacekeeper

Post by hgm »

Sazgr wrote: Thu Jan 19, 2023 4:11 pmWhat I was meaning is that if knight mobility bonuses are applied for only empty squares and squares occupied by opponent, then it is equivalent to applying penalties for protecting friendly pieces, which may not be worse.
I understood that; not counting Knight attacks on friendly pieces is equivalent to giving a bonus for not protecting your pieces with Knights. If other pieces are treated similarly, you are encouraging leaving pieces entirely unprotected.

A more elaborate scheme for mobility evaluation would use a 12x13 table mobilityBonus[coloredAttackerType][coloredTargetType], summed over all moves, so that you could give independent bonuses for attacks, protections and non-captures. (And perhaps Pawn captures and Pawn non-captures should be considered different attackerTypes, so that you get 14x13.) The simple method would have the mobilityBonus zero for equally colored attacker and target, and dependent only on the attackerType for the other elements. An even more elaborate scheme could use a 12x13x64 table mobilityBonus[coloredAttackerType][coloredTargetType][targetSquare].

Even more flexible would be to not directly tabulate the bonus, but instead a 'mobility code'. Which would be summed on a per-piece basis, and then used as an index in another table to convert it to a bonus in centi-Pawn. E.g. if you give each type of pseudo-legal Knight move a mobility code 1, you could have a 9-element table for Knights that gives a bonus per move, but subtracts a much larger penalty if there are 0 or 1 moves. And for a Rook you could give 1 for a sideway move, and 8 for a vertical move, and use a 64-element table that includes a penalty for having either the sideway or the horizontal moves be 0. (For the Knights you could likewise distinguish forward and backward moves by giving those code 1 or 5, and already give the penalty if there are no backward moves.)

Another approach, which focuses more on board control than mobility, would sum mobility codes per square, and use these as an index in a lookup table. You could organize this such that the sum of all moves to the square give a unique signature of the combination of pieces that attacks it. And then give a bonus that strongly correlates with the least-valuable piece that attacks it, and much more weakly with additional pieces.
Sazgr
Posts: 66
Joined: Thu Dec 09, 2021 8:26 pm
Full name: Kyle Zhang

Re: New engine: Peacekeeper

Post by Sazgr »

I do mobility by piece non-linearly so that each piece can be encouraged to be mobile

Code: Select all

Score of peacekeeper-more vs peacekeeper-less: 206 - 123 - 106  [0.595] 435
...      peacekeeper-more playing White: 115 - 52 - 50  [0.645] 217
...      peacekeeper-more playing Black: 91 - 71 - 56  [0.546] 218
...      White vs Black: 186 - 143 - 106  [0.549] 435
Elo difference: 67.1 +/- 28.8, LOS: 100.0 %, DrawRatio: 24.4 %
SPRT: llr 2.96 (100.5%), lbound -2.94, ubound 2.94 - H1 was accepted
I got a better result from mobility by only counting pawns as blockers and effectively making all pieces transparent. The above selfplay is without any other speed optimizations, even though the speed is reduced considerably, there are still large elo gains.

I tried using lithander's method to speed it up, but selfplay matches have proved inconclusive or a plain loss, because of a loss of accuracy with the increased speed.

Now I was considering to update mobility incrementally after each move, is this idea feasible or stupid and how might I make the incremental mobility updates cheap enough if so?