Skip to content

Commit 9afa03b

Browse files
noobpwnftwsnicolet
authored andcommitted
7-pieces Syzygy tablebase support
This is the first patch teaching Stockfish how to use the 7-pieces Syzygy tablebase currently calculated by Bujun Guo (@noobpwnftw) and Ronald de Man (@syzygy1). The 7-pieces database are so big that they required a change in the internal format of the files (technically, some DTZ values are 16 bits long, so this had to be stored as wide integers in the Huffman tree). Here are the estimated file size for the 7-pieces Syzygy files, compared to the 151G of the 6-pieces Syzygy: ``` 7.1T ./7men_testing/4v3_pawnful (ongoing, 120 of 325 sets remaining) 2.4T ./7men_testing/4v3_pawnless 2.3T ./7men_testing/5v2_pawnful 660G ./7men_testing/5v2_pawnless 117G ./7men_testing/6v1_pawnful 87G ./7men_testing/6v1_pawnless ``` Some pointers to download or recalculate the tables: Location of original files, by Bujun Guo: ftp://ftp.chessdb.cn/pub/syzygy/ Mirrors: http://tablebase.sesse.net/ (partial) http://tablebase.lichess.ovh/tables/standard/7/ Generator code: https://github.com/syzygy1/tb/ Closes #1707 Bench: 5591925 (No functional change if SyzygyTB is not used) ---------------------- Comment by Leonardo Ljubičić (@DragonMist) This is an amazing achievement, generating and being able to use 7 men syzygy on the fly. Thank you for your efforts @noobpwnftw !! Looking forward how this will work in real life, and expecting some trade off between gaining perfect play and slow disc Access, but once the disc speed and space is not a problem, I expect 7 men to yield something like 30 elo at least. ----------------------- Comment by Michael Byrne (@MichaelB7) This definitely has a bright future. I turned off the 50 move rule (ala ICCF new rules) for the following position: `[d]8/8/1b6/8/4N2r/1k6/7B/R1K5 w - - 0 1` This position is a 451 ply win for white (sans the 50 move rule, this position was identified by the generator as the longest cursed win for white in KRBN v KRB). Now Stockfish finds it instantly (as it should), nice work 👊👍 . ``` dep score nodes time 7 +132.79 4339 0:00.00 Rb1+ Kc4 Nd6+ Kc5 Bg1+ Kxd6 Rxb6+ Kc7 Be3 Rh2 Bd4 6 +132.79 1652 0:00.00 Rb1+ Kc4 Nd2+ Kd5 Rxb6 Rxh2 Nf3 Rf2 5 +132.79 589 0:00.00 Rb1+ Kc4 Rxb6 Rxh2 Nf6 Rh1+ Kb2 4 +132.79 308 0:00.00 Rb1+ Kc4 Nd6+ Kc3 Rxb6 Rxh2 3 +132.79 88 0:00.00 Rb1+ Ka4 Nc3+ Ka5 Ra1+ Kb4 Ra4+ Kxc3 Rxh4 2 +132.79 54 0:00.00 Rb1+ Ka4 Nc3+ Ka5 Ra1+ Kb4 1 +132.7 ```
1 parent ba2a2c3 commit 9afa03b

File tree

2 files changed

+48
-23
lines changed

2 files changed

+48
-23
lines changed

src/syzygy/tbprobe.cpp

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ int Tablebases::MaxCardinality;
5555

5656
namespace {
5757

58-
constexpr int TBPIECES = 6; // Max number of supported pieces
58+
constexpr int TBPIECES = 7; // Max number of supported pieces
5959

6060
enum { BigEndian, LittleEndian };
6161
enum TBType { KEY, WDL, DTZ }; // Used as template parameter
6262

6363
// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables
64-
enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, SingleValue = 128 };
64+
enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, SingleValue = 128 };
6565

6666
inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); }
6767
inline Square operator^=(Square& s, int i) { return s = Square(int(s) ^ i); }
@@ -75,8 +75,8 @@ int MapA1D1D4[SQUARE_NB];
7575
int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB]
7676

7777
int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements
78-
int LeadPawnIdx[5][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB]
79-
int LeadPawnsSize[5][4]; // [leadPawnsCnt][FILE_A..FILE_D]
78+
int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB]
79+
int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D]
8080

8181
// Comparison function to sort leading pawns in ascending MapPawns[] order
8282
bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; }
@@ -144,16 +144,15 @@ static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes");
144144
typedef uint16_t Sym; // Huffman symbol
145145

146146
struct LR {
147-
enum Side { Left, Right, Value };
147+
enum Side { Left, Right };
148148

149149
uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12
150150
// bits is the right-hand symbol. If symbol has length 1,
151-
// then the first byte is the stored value.
151+
// then the left-hand symbol is the stored value.
152152
template<Side S>
153153
Sym get() {
154154
return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] :
155-
S == Right ? (lr[2] << 4) | (lr[1] >> 4) :
156-
S == Value ? lr[0] : (assert(false), Sym(-1));
155+
S == Right ? (lr[2] << 4) | (lr[1] >> 4) : (assert(false), Sym(-1));
157156
}
158157
};
159158

@@ -385,7 +384,7 @@ class TBTables {
385384

386385
typedef std::tuple<Key, TBTable<WDL>*, TBTable<DTZ>*> Entry;
387386

388-
static const int Size = 1 << 12; // 4K table, indexed by key's 12 lsb
387+
static const int Size = 1 << 16; // 64K table, indexed by key's 16 lsb
389388

390389
Entry hashTable[Size];
391390

@@ -512,7 +511,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
512511
offset -= d->blockLength[block++] + 1;
513512

514513
// Finally, we find the start address of our block of canonical Huffman symbols
515-
uint32_t* ptr = (uint32_t*)(d->data + block * d->sizeofBlock);
514+
uint32_t* ptr = (uint32_t*)(d->data + ((uint64_t)block * d->sizeofBlock));
516515

517516
// Read the first 64 bits in our block, this is a (truncated) sequence of
518517
// unknown number of symbols of unknown length but we know the first one
@@ -575,7 +574,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
575574
}
576575
}
577576

578-
return d->btree[sym].get<LR::Value>();
577+
return d->btree[sym].get<LR::Left>();
579578
}
580579

581580
bool check_dtz_stm(TBTable<WDL>*, int, File) { return true; }
@@ -601,8 +600,12 @@ int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
601600

602601
uint8_t* map = entry->map;
603602
uint16_t* idx = entry->get(0, f)->map_idx;
604-
if (flags & TBFlag::Mapped)
605-
value = map[idx[WDLMap[wdl + 2]] + value];
603+
if (flags & TBFlag::Mapped) {
604+
if (flags & TBFlag::Wide)
605+
value = ((uint16_t *)map)[idx[WDLMap[wdl + 2]] + value];
606+
else
607+
value = map[idx[WDLMap[wdl + 2]] + value];
608+
}
606609

607610
// DTZ tables store distance to zero in number of moves or plies. We
608611
// want to return plies, so we have convert to plies when needed.
@@ -994,11 +997,22 @@ uint8_t* set_dtz_map(TBTable<DTZ>& e, uint8_t* data, File maxFile) {
994997
e.map = data;
995998

996999
for (File f = FILE_A; f <= maxFile; ++f) {
997-
if (e.get(0, f)->flags & TBFlag::Mapped)
998-
for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x
999-
e.get(0, f)->map_idx[i] = (uint16_t)(data - e.map + 1);
1000-
data += *data + 1;
1000+
auto flags = e.get(0, f)->flags;
1001+
if (flags & TBFlag::Mapped) {
1002+
if (flags & TBFlag::Wide) {
1003+
data += (uintptr_t)data & 1; // Word alignment, we may have a mixed table
1004+
for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x
1005+
e.get(0, f)->map_idx[i] = (uint16_t)((uint16_t *)data - (uint16_t *)e.map + 1);
1006+
data += 2 * number<uint16_t, LittleEndian>(data) + 2;
1007+
}
10011008
}
1009+
else {
1010+
for (int i = 0; i < 4; ++i) {
1011+
e.get(0, f)->map_idx[i] = (uint16_t)(data - e.map + 1);
1012+
data += *data + 1;
1013+
}
1014+
}
1015+
}
10021016
}
10031017

10041018
return data += (uintptr_t)data & 1; // Word alignment
@@ -1274,9 +1288,9 @@ void Tablebases::init(const std::string& paths) {
12741288
// among pawns with same file, the one with lowest rank.
12751289
int availableSquares = 47; // Available squares when lead pawn is in a2
12761290

1277-
// Init the tables for the encoding of leading pawns group: with 6-men TB we
1278-
// can have up to 4 leading pawns (KPPPPK).
1279-
for (int leadPawnsCnt = 1; leadPawnsCnt <= 4; ++leadPawnsCnt)
1291+
// Init the tables for the encoding of leading pawns group: with 7-men TB we
1292+
// can have up to 5 leading pawns (KPPPPPK).
1293+
for (int leadPawnsCnt = 1; leadPawnsCnt <= 5; ++leadPawnsCnt)
12801294
for (File f = FILE_A; f <= FILE_D; ++f)
12811295
{
12821296
// Restart the index at every file because TB table is splitted
@@ -1320,11 +1334,22 @@ void Tablebases::init(const std::string& paths) {
13201334
for (PieceType p3 = PAWN; p3 <= p2; ++p3) {
13211335
TBTables.add({KING, p1, p2, p3, KING});
13221336

1323-
for (PieceType p4 = PAWN; p4 <= p3; ++p4)
1337+
for (PieceType p4 = PAWN; p4 <= p3; ++p4) {
13241338
TBTables.add({KING, p1, p2, p3, p4, KING});
13251339

1326-
for (PieceType p4 = PAWN; p4 < KING; ++p4)
1340+
for (PieceType p5 = PAWN; p5 <= p4; ++p5)
1341+
TBTables.add({KING, p1, p2, p3, p4, p5, KING});
1342+
1343+
for (PieceType p5 = PAWN; p5 < KING; ++p5)
1344+
TBTables.add({KING, p1, p2, p3, p4, KING, p5});
1345+
}
1346+
1347+
for (PieceType p4 = PAWN; p4 < KING; ++p4) {
13271348
TBTables.add({KING, p1, p2, p3, KING, p4});
1349+
1350+
for (PieceType p5 = PAWN; p5 <= p4; ++p5)
1351+
TBTables.add({KING, p1, p2, p3, KING, p4, p5});
1352+
}
13281353
}
13291354

13301355
for (PieceType p3 = PAWN; p3 <= p1; ++p3)

src/ucioption.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ void init(OptionsMap& o) {
7676
o["SyzygyPath"] << Option("<empty>", on_tb_path);
7777
o["SyzygyProbeDepth"] << Option(1, 1, 100);
7878
o["Syzygy50MoveRule"] << Option(true);
79-
o["SyzygyProbeLimit"] << Option(6, 0, 6);
79+
o["SyzygyProbeLimit"] << Option(7, 0, 7);
8080
}
8181

8282

0 commit comments

Comments
 (0)