Skip to content

Commit

Permalink
Syzygy tablebases
Browse files Browse the repository at this point in the history
Adds support for Syzygy tablebases to Stockfish.  See
the Readme for information on using the tablebases.

Tablebase support can be enabled/disabled at the Makefile
level as well, by setting syzygy=yes or syzygy=no.

Big/little endian are both supported.

No functional change (if Tablebases are not used).

Resolves #6
  • Loading branch information
Ronald de Man authored and glinscott committed Nov 25, 2014
1 parent 4509eb1 commit 7caa6cd
Show file tree
Hide file tree
Showing 12 changed files with 2,837 additions and 6 deletions.
54 changes: 54 additions & 0 deletions Readme.md
Expand Up @@ -12,6 +12,8 @@ to one search thread, so it is therefore recommended to inspect the value of
the *Threads* UCI parameter, and to make sure it equals the number of CPU
cores on your computer.

This version of Stockfish has support for Syzygybases.


### Files

Expand All @@ -25,6 +27,58 @@ This distribution of Stockfish consists of the following files:
that can be used to compile Stockfish on Unix-like systems.


### Syzygybases

**Configuration**

Syzygybases are configured using the UCI options "SyzygyPath",
"SyzygyProbeDepth", "Syzygy50MoveRule" and "SyzygyProbeLimit".

The option "SyzygyPath" should be set to the directory or directories that
contain the .rtbw and .rtbz files. Multiple directories should be
separated by ";" on Windows and by ":" on Unix-based operating systems.
**Do not use spaces around the ";" or ":".**

Example: `C:\tablebases\wdl345;C:\tablebases\wdl6;D:\tablebases\dtz345;D:\tablebases\dtz6`

It is recommended to store .rtbw files on an SSD. There is no loss in
storing the .rtbz files on a regular HD.

Increasing the "SyzygyProbeDepth" option lets the engine probe less
aggressively. Set this option to a higher value if you experience too much
slowdown (in terms of nps) due to TB probing.

Set the "Syzygy50MoveRule" option to false if you want tablebase positions
that are drawn by the 50-move rule to count as win or loss. This may be useful
for correspondence games (because of tablebase adjudication).

The "SyzygyProbeLimit" option should normally be left at its default value.

**What to expect**
If the engine is searching a position that is not in the tablebases (e.g.
a position with 7 pieces), it will access the tablebases during the search.
If the engine reports a very large score (typically 123.xx), this means
that it has found a winning line into a tablebase position.

If the engine is given a position to search that is in the tablebases, it
will use the tablebases at the beginning of the search to preselect all
good moves, i.e. all moves that preserve the win or preserve the draw while
taking into account the 50-move rule.
It will then perform a search only on those moves. **The engine will not move
immediately**, unless there is only a single good move. **The engine likely
will not report a mate score even if the position is known to be won.**

It is therefore clear that behaviour is not identical to what one might
be used to with Nalimov tablebases. There are technical reasons for this
difference, the main technical reason being that Nalimov tablebases use the
DTM metric (distance-to-mate), while Syzygybases use a variation of the
DTZ metric (distance-to-zero, zero meaning any move that resets the 50-move
counter). This special metric is one of the reasons that Syzygybases are
more compact than Nalimov tablebases, while still storing all information
needed for optimal play and in addition being able to take into account
the 50-move rule.


### Compiling it yourself

On Unix-like systems, it should be possible to compile Stockfish
Expand Down
8 changes: 7 additions & 1 deletion src/Makefile
Expand Up @@ -75,6 +75,12 @@ bsfq = no
popcnt = no
sse = no
pext = no
syzygy = yes

ifeq ($(syzygy),yes)
OBJS += syzygy/tbprobe.o
CXXFLAGS += -DSYZYGY
endif

### 2.2 Architecture specific

Expand Down Expand Up @@ -398,7 +404,7 @@ install:
-strip $(BINDIR)/$(EXE)

clean:
$(RM) $(EXE) $(EXE).exe *.o .depend *~ core bench.txt *.gcda
$(RM) $(EXE) $(EXE).exe *.o .depend *~ core bench.txt *.gcda ./syzygy/*.o

default:
help
Expand Down
7 changes: 7 additions & 0 deletions src/main.cpp
Expand Up @@ -27,6 +27,10 @@
#include "tt.h"
#include "uci.h"

#ifdef SYZYGY
#include "syzygy/tbprobe.h"
#endif

int main(int argc, char* argv[]) {

std::cout << engine_info() << std::endl;
Expand All @@ -40,6 +44,9 @@ int main(int argc, char* argv[]) {
Pawns::init();
Threads.init();
TT.resize(Options["Hash"]);
#ifdef SYZYGY
Tablebases::init(Options["SyzygyPath"]);
#endif

UCI::loop(argc, argv);

Expand Down
16 changes: 15 additions & 1 deletion src/position.h
Expand Up @@ -24,9 +24,9 @@
#include <cstddef>

#include "bitboard.h"
#include "bitcount.h"
#include "types.h"


/// The checkInfo struct is initialized at c'tor time and keeps info used
/// to detect if a move gives check.
class Position;
Expand Down Expand Up @@ -100,6 +100,7 @@ class Position {
bool empty(Square s) const;
template<PieceType Pt> int count(Color c) const;
template<PieceType Pt> const Square* list(Color c) const;
int total_piece_count() const;

// Castling
int can_castle(Color c) const;
Expand Down Expand Up @@ -166,6 +167,7 @@ class Position {
uint64_t nodes_searched() const;
void set_nodes_searched(uint64_t n);
bool is_draw() const;
int rule50_count() const;

// Position consistency check, for debugging
bool pos_is_ok(int* step = NULL) const;
Expand Down Expand Up @@ -352,6 +354,14 @@ inline int Position::game_ply() const {
return gamePly;
}

inline int Position::rule50_count() const {
return st->rule50;
}

inline int Position::total_piece_count() const {
return HasPopCnt ? popcount<Full>(pieces()) : pieceCount[WHITE][ALL_PIECES];
}

inline bool Position::opposite_bishops() const {

return pieceCount[WHITE][BISHOP] == 1
Expand Down Expand Up @@ -402,6 +412,8 @@ inline void Position::put_piece(Square s, Color c, PieceType pt) {
byColorBB[c] |= s;
index[s] = pieceCount[c][pt]++;
pieceList[c][pt][index[s]] = s;
if (!HasPopCnt)
pieceCount[WHITE][ALL_PIECES]++;
}

inline void Position::move_piece(Square from, Square to, Color c, PieceType pt) {
Expand Down Expand Up @@ -432,6 +444,8 @@ inline void Position::remove_piece(Square s, Color c, PieceType pt) {
index[lastSquare] = index[s];
pieceList[c][pt][index[lastSquare]] = lastSquare;
pieceList[c][pt][pieceCount[c][pt]] = SQ_NONE;
if (!HasPopCnt)
pieceCount[WHITE][ALL_PIECES]--;
}

#endif // #ifndef POSITION_H_INCLUDED
111 changes: 110 additions & 1 deletion src/search.cpp
Expand Up @@ -34,6 +34,10 @@
#include "tt.h"
#include "uci.h"

#ifdef SYZYGY
#include "syzygy/tbprobe.h"
#endif

namespace Search {

volatile SignalsType Signals;
Expand All @@ -42,6 +46,12 @@ namespace Search {
Position RootPos;
Time::point SearchTime;
StateStackPtr SetupStates;
int TBCardinality;
uint64_t TBHits;
bool RootInTB;
bool TB50MoveRule;
Depth TBProbeDepth;
Value TBScore;
}

using std::string;
Expand Down Expand Up @@ -181,6 +191,8 @@ template uint64_t Search::perft<true>(Position& pos, Depth depth);
void Search::think() {

TimeMgr.init(Limits, RootPos.game_ply(), RootPos.side_to_move());
TBHits = TBCardinality = 0;
RootInTB = false;

int cf = Options["Contempt"] * PawnValueEg / 100; // From centipawns
DrawValue[ RootPos.side_to_move()] = VALUE_DRAW - Value(cf);
Expand All @@ -195,6 +207,60 @@ void Search::think() {
}
else
{
#ifdef SYZYGY
// Check Tablebases at root
int piecesCnt = RootPos.total_piece_count();
TBCardinality = Options["SyzygyProbeLimit"];
TBProbeDepth = Options["SyzygyProbeDepth"] * ONE_PLY;
if (TBCardinality > Tablebases::TBLargest)
{
TBCardinality = Tablebases::TBLargest;
TBProbeDepth = 0 * ONE_PLY;
}
TB50MoveRule = Options["Syzygy50MoveRule"];

if (piecesCnt <= TBCardinality)
{
TBHits = RootMoves.size();

// If the current root position is in the tablebases then RootMoves
// contains only moves that preserve the draw or win.
RootInTB = Tablebases::root_probe(RootPos, TBScore);

if (RootInTB)
{
TBCardinality = 0; // Do not probe tablebases during the search

// It might be a good idea to mangle the hash key (xor it
// with a fixed value) in order to "clear" the hash table of
// the results of previous probes. However, that would have to
// be done from within the Position class, so we skip it for now.

// Optional: decrease target time.
}
else // If DTZ tables are missing, use WDL tables as a fallback
{
// Filter out moves that do not preserve a draw or win
RootInTB = Tablebases::root_probe_wdl(RootPos, TBScore);

// Only probe during search if winning
if (TBScore <= VALUE_DRAW)
TBCardinality = 0;
}

if (!RootInTB)
{
TBHits = 0;
}
else if (!TB50MoveRule)
{
TBScore = TBScore > VALUE_DRAW ? VALUE_MATE - MAX_PLY - 1
: TBScore < VALUE_DRAW ? -VALUE_MATE + MAX_PLY + 1
: TBScore;
}
}
#endif

for (size_t i = 0; i < Threads.size(); ++i)
Threads[i]->maxPly = 0;

Expand Down Expand Up @@ -486,6 +552,39 @@ namespace {
return ttValue;
}

#ifdef SYZYGY
// Step 4a. Tablebase probe
if ( !RootNode
&& pos.total_piece_count() <= TBCardinality
&& ( pos.total_piece_count() < TBCardinality || depth >= TBProbeDepth )
&& pos.rule50_count() == 0)
{
int found, v = Tablebases::probe_wdl(pos, &found);

if (found)
{
TBHits++;

if (TB50MoveRule) {
value = v < -1 ? -VALUE_MATE + MAX_PLY + ss->ply
: v > 1 ? VALUE_MATE - MAX_PLY - ss->ply
: VALUE_DRAW + 2 * v;
}
else
{
value = v < 0 ? -VALUE_MATE + MAX_PLY + ss->ply
: v > 0 ? VALUE_MATE - MAX_PLY - ss->ply
: VALUE_DRAW;
}

TT.store(posKey, value_to_tt(value, ss->ply), BOUND_EXACT,
std::min(DEPTH_MAX - ONE_PLY, depth + 6 * ONE_PLY), MOVE_NONE, VALUE_NONE);

return value;
}
}
#endif

// Step 5. Evaluate the position statically and update parent's gain statistics
if (inCheck)
{
Expand Down Expand Up @@ -1352,15 +1451,25 @@ namespace {
Depth d = updated ? depth : depth - ONE_PLY;
Value v = updated ? RootMoves[i].score : RootMoves[i].prevScore;

bool tb = RootInTB;
if (tb)
{
if (abs(v) >= VALUE_MATE - MAX_PLY)
tb = false;
else
v = TBScore;
}

if (ss.rdbuf()->in_avail()) // Not at first line
ss << "\n";

ss << "info depth " << d / ONE_PLY
<< " seldepth " << selDepth
<< " multipv " << i + 1
<< " score " << (i == PVIdx ? UCI::format_value(v, alpha, beta) : UCI::format_value(v))
<< " score " << ((!tb && i == PVIdx) ? UCI::format_value(v, alpha, beta) : UCI::format_value(v))
<< " nodes " << pos.nodes_searched()
<< " nps " << pos.nodes_searched() * 1000 / elapsed
<< " tbhits " << TBHits
<< " time " << elapsed
<< " pv";

Expand Down

0 comments on commit 7caa6cd

Please sign in to comment.