Skip to content

Commit

Permalink
syzygy (#76)
Browse files Browse the repository at this point in the history
bench: 1944878
ELO   | 1.87 +- 3.83 (95%)
SPRT  | 8.0+0.08s Threads=1 Hash=32MB
LLR   | 2.96 (-2.94, 2.94) [-5.00, 0.00]
GAMES | N: 12848 W: 2656 L: 2587 D: 7605
ELO   | 9.49 +- 5.61 (95%)
SPRT  | 8.0+0.08s Threads=1 Hash=32MB
LLR   | 2.96 (-2.94, 2.94) [0.00, 5.00]
GAMES | N: 5528 W: 1114 L: 963 D: 3451
  • Loading branch information
connormcmonigle authored Oct 8, 2021
1 parent d9c95a0 commit 6089a7b
Show file tree
Hide file tree
Showing 15 changed files with 4,817 additions and 14 deletions.
9 changes: 6 additions & 3 deletions build/makefile
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
EXE = seer
SRC = ../src/seer.cc
SRC = ../src/seer.cc ../syzygy/*.c
CC = g++

EVALFILE = weights/0xf8175070.bin

OPSLIMIT = 1000000000
CXXSTANDARD = 17

INCLUDE = ../include
INCBIN = ../incbin
SYZYGY = ../syzygy

CXXFLAGS += -std=c++$(CXXSTANDARD)
CXXFLAGS += -O3 -g -DNDEBUG -march=native -mtune=native -fopenmp
CXXFLAGS += -O3 -g -DNDEBUG -march=native -mtune=native -fopenmp -flto
CXXFLAGS += -Wall -Wextra
CXXFLAGS += -fconstexpr-ops-limit=$(OPSLIMIT)
CXXFLAGS += -I$(INCLUDE)
CXXFLAGS += -I$(INCLUDE) -I$(INCBIN) -I$(SYZYGY)
CXXFLAGS += -DEVALFILE=\"$(EVALFILE)\"

THREADSANFLAGS += $(CXXFLAGS) -fsanitize=thread
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion include/embedded_weights.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

#define INCBIN_PREFIX
#define INCBIN_STYLE INCBIN_STYLE_SNAKE
#include <incbin/incbin.h>
#include <incbin.h>

namespace embed {

Expand Down
8 changes: 7 additions & 1 deletion include/search_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ inline constexpr score_type max_mate_score = -2 * big_number;

inline constexpr score_type mate_score = max_mate_score - (max_depth + max_depth_margin);

inline constexpr score_type tb_win_score = big_number + 1;

inline constexpr score_type tb_loss_score = -tb_win_score;

inline constexpr score_type draw_score = 0;

inline constexpr score_type aspiration_delta = 20;
Expand Down Expand Up @@ -97,7 +101,9 @@ struct fixed_constants {
return lmr_tbl[std::min(last_idx, depth) * lmr_tbl_dim + std::min(last_idx, move_idx)];
}

constexpr depth_type nmp_reduction(const depth_type& depth, const score_type& beta, const score_type& value) const { return 4 + depth / 6 + std::min(3, (value - beta) / 256); }
constexpr depth_type nmp_reduction(const depth_type& depth, const score_type& beta, const score_type& value) const {
return 4 + depth / 6 + std::min(3, (value - beta) / 256);
}

constexpr see_type nmp_see_threshold() const { return 200; }

Expand Down
4 changes: 3 additions & 1 deletion include/search_stack.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ struct stack_view {
stack* view_;
depth_type height_{};

constexpr score_type effective_mate_score() const { return mate_score + height_; }
constexpr score_type loss_score() const { return mate_score + height_; }

constexpr score_type win_score() const { return -mate_score - height_; }

bool reached_max_height() const { return height_ >= (safe_depth_ - 1); }

Expand Down
104 changes: 104 additions & 0 deletions include/syzygy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#pragma once

#include <board.h>
#include <search_constants.h>
#include <tbprobe.h>
#include <transposition_table.h>

#include <string>

namespace syzygy {

enum class wdl_type {
loss,
draw,
win
};

struct tb_wdl_result {
bool success;
wdl_type wdl{wdl_type::draw};

static constexpr tb_wdl_result failure() { return tb_wdl_result{false}; }
static constexpr tb_wdl_result from_value(const unsigned int& value) {
if (value == TB_WIN) { return tb_wdl_result{true, wdl_type::win}; }
if (value == TB_DRAW) { return tb_wdl_result{true, wdl_type::draw}; }
if (value == TB_LOSS) { return tb_wdl_result{true, wdl_type::loss}; }
return failure();
}
};

struct tb_dtz_result {
bool success;
search::score_type score{search::draw_score};
chess::move move{chess::move::null()};

static constexpr tb_dtz_result failure() { return tb_dtz_result{false}; }
static tb_dtz_result from_value(const chess::board& bd, const unsigned int& value) {
auto is_same_promo = [](const chess::move& mv, const int& promo) {
constexpr int num_pieces = 6;
return ((!mv.is_promotion() && promo == 0) || (mv.is_promotion() && (num_pieces - promo - 1) == static_cast<int>(mv.promotion())));
};

if (value == TB_RESULT_FAILED || value == TB_RESULT_CHECKMATE || value == TB_RESULT_STALEMATE) { failure(); }
const int wdl = TB_GET_WDL(value);

const chess::move dtz_move = [&] {
const chess::move_list list = bd.generate_moves();
const int promo = TB_GET_PROMOTES(value);
const int from = TB_GET_FROM(value);
const int to = TB_GET_TO(value);
const auto it = std::find_if(list.begin(), list.end(), [&](const chess::move& mv) {
return mv.from().index() == from && mv.to().index() == to && is_same_promo(mv, promo);
});
if (it != list.end()) { return *it; }
return chess::move::null();
}();

if (dtz_move == chess::move::null()) { return failure(); }

if (wdl == TB_WIN) { return tb_dtz_result{true, search::tb_win_score, dtz_move}; }
if (wdl == TB_LOSS) { return tb_dtz_result{true, search::tb_loss_score, dtz_move}; }
return tb_dtz_result{true, search::draw_score, dtz_move};
}
};

tb_wdl_result probe_wdl(const chess::board& bd) {
if (bd.num_pieces() > TB_LARGEST || bd.lat_.half_clock != 0) { return tb_wdl_result::failure(); }
if (bd.lat_.white.oo() || bd.lat_.white.ooo() || bd.lat_.black.oo() || bd.lat_.black.ooo()) { return tb_wdl_result::failure(); }

constexpr unsigned int rule_50 = 0;
constexpr unsigned int castling_rights = 0;
const unsigned int ep = bd.lat_.them(bd.turn()).ep_mask().any() ? bd.lat_.them(bd.turn()).ep_mask().item().index() : 0;
const bool turn = bd.turn();

const unsigned value = tb_probe_wdl(
bd.man_.white.all().data, bd.man_.black.all().data, (bd.man_.white.king() | bd.man_.black.king()).data,
(bd.man_.white.queen() | bd.man_.black.queen()).data, (bd.man_.white.rook() | bd.man_.black.rook()).data,
(bd.man_.white.bishop() | bd.man_.black.bishop()).data, (bd.man_.white.knight() | bd.man_.black.knight()).data,
(bd.man_.white.pawn() | bd.man_.black.pawn()).data, rule_50, castling_rights, ep, turn);

return tb_wdl_result::from_value(value);
}

tb_dtz_result probe_dtz(const chess::board& bd) {
if (bd.num_pieces() > TB_LARGEST) { return tb_dtz_result::failure(); }
if (bd.lat_.white.oo() || bd.lat_.white.ooo() || bd.lat_.black.oo() || bd.lat_.black.ooo()) { return tb_dtz_result::failure(); }

const unsigned int rule_50 = bd.lat_.half_clock;
constexpr unsigned int castling_rights = 0;
const unsigned int ep = bd.lat_.them(bd.turn()).ep_mask().any() ? bd.lat_.them(bd.turn()).ep_mask().item().index() : 0;
const bool turn = bd.turn();

const unsigned value = tb_probe_root(
bd.man_.white.all().data, bd.man_.black.all().data, (bd.man_.white.king() | bd.man_.black.king()).data,
(bd.man_.white.queen() | bd.man_.black.queen()).data, (bd.man_.white.rook() | bd.man_.black.rook()).data,
(bd.man_.white.bishop() | bd.man_.black.bishop()).data, (bd.man_.white.knight() | bd.man_.black.knight()).data,
(bd.man_.white.pawn() | bd.man_.black.pawn()).data, rule_50, castling_rights, ep, turn, nullptr);

return tb_dtz_result::from_value(bd, value);
}

void init(const std::string& path) { tb_init(path.c_str()); }

} // namespace syzygy
30 changes: 24 additions & 6 deletions include/thread_worker.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <nnue_model.h>
#include <search_constants.h>
#include <search_stack.h>
#include <syzygy.h>
#include <transposition_table.h>

#include <atomic>
Expand Down Expand Up @@ -61,6 +62,7 @@ struct internal_state {

std::atomic_bool is_stable{false};
std::atomic_size_t nodes{};
std::atomic_size_t tb_hits{};
std::atomic<search::depth_type> depth{};

std::atomic<search::score_type> score{};
Expand All @@ -81,6 +83,7 @@ struct internal_state {
cache.clear();
is_stable.store(false);
nodes.store(0);
tb_hits.store(0);
depth.store(0);
score.store(0);
best_move.store(move::null().data);
Expand Down Expand Up @@ -182,7 +185,7 @@ struct thread_worker {
const move_list list = bd.generate_noisy_moves();
const bool is_check = bd.is_check();

if (list.size() == 0 && is_check) { return ss.effective_mate_score(); }
if (list.size() == 0 && is_check) { return ss.loss_score(); }
if (ss.is_two_fold(bd.hash())) { return search::draw_score; }
if (bd.is_trivially_drawn()) { return search::draw_score; }

Expand All @@ -199,7 +202,7 @@ struct thread_worker {

const auto [static_value, value] = [&] {
const auto maybe_eval = internal.cache.find(bd.hash());
const search::score_type static_value = is_check ? ss.effective_mate_score() :
const search::score_type static_value = is_check ? ss.loss_score() :
!is_pv && maybe_eval.has_value() ? maybe_eval.value() :
eval.evaluate(bd.turn());

Expand Down Expand Up @@ -290,11 +293,15 @@ struct thread_worker {
const move_list list = bd.generate_moves();
const bool is_check = bd.is_check();

if (list.size() == 0 && is_check) { return make_result(ss.effective_mate_score(), move::null()); }
if (list.size() == 0 && is_check) { return make_result(ss.loss_score(), move::null()); }
if (list.size() == 0) { return make_result(search::draw_score, move::null()); }
if (!is_root && ss.is_two_fold(bd.hash())) { return make_result(search::draw_score, move::null()); }
if (!is_root && bd.is_trivially_drawn()) { return make_result(search::draw_score, move::null()); }

if constexpr (is_root) {
if (const syzygy::tb_dtz_result result = syzygy::probe_dtz(bd); result.success) { return make_result(result.score, result.move); }
}

const search::score_type original_alpha = alpha;

// step 3. initialize move orderer (setting tt move first if applicable)
Expand All @@ -314,14 +321,24 @@ struct thread_worker {
orderer.set_first(entry.best_move());
}

if (const syzygy::tb_wdl_result result = syzygy::probe_wdl(bd); !is_root && result.success) {
++internal.tb_hits;

switch (result.wdl) {
case syzygy::wdl_type::loss: return make_result(ss.loss_score(), move::null());
case syzygy::wdl_type::draw: return make_result(search::draw_score, move::null());
case syzygy::wdl_type::win: return make_result(ss.win_score(), move::null());
}
}

// step 4. internal iterative reductions
const bool should_iir = !maybe.has_value() && !ss.has_excluded() && depth >= external.constants->iir_depth();
if (should_iir) { --depth; }

// step 5. compute static eval and adjust appropriately if there's a tt hit
const auto [static_value, value] = [&] {
const auto maybe_eval = internal.cache.find(bd.hash());
const search::score_type static_value = is_check ? ss.effective_mate_score() :
const search::score_type static_value = is_check ? ss.loss_score() :
!is_pv && maybe_eval.has_value() ? maybe_eval.value() :
eval.evaluate(bd.turn());

Expand All @@ -345,7 +362,7 @@ struct thread_worker {

// step 8. static null move pruning
const bool snm_prune = !is_pv && !ss.has_excluded() && !is_check && depth <= external.constants->snmp_depth() &&
value > beta + external.constants->snmp_margin(improving, depth) && value > ss.effective_mate_score();
value > beta + external.constants->snmp_margin(improving, depth) && value > ss.loss_score();

if (snm_prune) { return make_result(value, move::null()); }

Expand Down Expand Up @@ -375,7 +392,7 @@ struct thread_worker {
move_list quiets_tried{};

// move loop
search::score_type best_score = ss.effective_mate_score();
search::score_type best_score = ss.loss_score();
move best_move = *list.begin();

bool did_double_extend = false;
Expand Down Expand Up @@ -598,6 +615,7 @@ struct thread_worker {
void go(const position_history& hist, const board& bd, const search::depth_type& start_depth) {
loop.next([hist, bd, start_depth, this] {
internal.nodes.store(0);
internal.tb_hits.store(0);
internal.depth.store(start_depth);
internal.is_stable.store(false);
internal.best_move.store(bd.generate_moves().begin()->data);
Expand Down
24 changes: 22 additions & 2 deletions include/uci.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <option_parser.h>
#include <search_constants.h>
#include <search_stack.h>
#include <syzygy.h>
#include <thread_worker.h>
#include <time_manager.h>
#include <version.h>
Expand Down Expand Up @@ -112,7 +113,9 @@ struct uci {
book_info_string();
});

return uci_options(weight_path, hash_size, thread_count, ponder, own_book, book_path);
auto syzygy_path = option_callback(string_option("SyzygyPath"), [this](const std::string& path) { syzygy::init(path); });

return uci_options(weight_path, hash_size, thread_count, own_book, book_path, syzygy_path);
}

void uci_new_game() {
Expand Down Expand Up @@ -164,7 +167,7 @@ struct uci {
const size_t nps = std::chrono::milliseconds(std::chrono::seconds(1)).count() * nodes / (1 + elapsed_ms);
if (is_searching() && depth < search::max_depth) {
os << "info depth " << depth << " seldepth " << worker.internal.stack.sel_depth() << " score cp " << score << " nodes " << nodes << " nps "
<< nps << " time " << elapsed_ms << " pv " << worker.internal.stack.pv_string() << std::endl;
<< nps << " time " << elapsed_ms << " tbhits " << worker.internal.tb_hits << " pv " << worker.internal.stack.pv_string() << std::endl;
}
}

Expand Down Expand Up @@ -222,6 +225,21 @@ struct uci {
os << "score: " << evaluator.evaluate(position.turn()) << std::endl;
}

void probe() {
std::lock_guard<std::mutex> os_lk(os_mutex_);
if (const syzygy::tb_wdl_result result = syzygy::probe_wdl(position); result.success) {
std::cout << "success: " << [&]{
switch (result.wdl) {
case syzygy::wdl_type::loss: return "loss";
case syzygy::wdl_type::draw: return "draw";
case syzygy::wdl_type::win: return "win";
}
}() << std::endl;
} else {
std::cout << "fail" << std::endl;
}
}

void see() {
std::lock_guard<std::mutex> os_lk(os_mutex_);
for (const chess::move& mv : position.generate_moves()) {
Expand Down Expand Up @@ -263,6 +281,8 @@ struct uci {
eval();
} else if (!is_searching() && line == "see") {
see();
} else if (!is_searching() && line == "probe") {
probe();
} else if (!is_searching() && std::regex_match(line, perft_rgx)) {
perft(line);
} else if (!is_searching() && std::regex_match(line, go_rgx)) {
Expand Down
23 changes: 23 additions & 0 deletions syzygy/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
The MIT License (MIT)

Copyright (c) 2015 basil00
Modifications Copyright (c) 2016-2017 by Jon Dart

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Loading

0 comments on commit 6089a7b

Please sign in to comment.