Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add MultiPV support to Koivisto (#216)
  • Loading branch information
mhouppin committed Jun 17, 2022
1 parent 3c09930 commit df558f8
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 75 deletions.
18 changes: 9 additions & 9 deletions src_files/history.h
Expand Up @@ -26,24 +26,24 @@
struct SearchData {
move::Move bestMove = 0;
// Effort spent
int64_t spentEffort[bb::N_SQUARES][bb::N_SQUARES] = {0};
int64_t spentEffort[bb::N_SQUARES][bb::N_SQUARES] = {{0}};
// EvalImprovement
int maxImprovement[bb::N_SQUARES][bb::N_SQUARES] = {0};
int maxImprovement[bb::N_SQUARES][bb::N_SQUARES] = {{0}};
// capture history table (side-from-to)
int captureHistory[bb::N_COLORS][bb::N_SQUARES * bb::N_SQUARES] = {0};
int captureHistory[bb::N_COLORS][bb::N_SQUARES * bb::N_SQUARES] = {{0}};
// threat history
int th[bb::N_COLORS][bb::N_SQUARES + 1][bb::N_SQUARES * bb::N_SQUARES] = {0};
int th[bb::N_COLORS][bb::N_SQUARES + 1][bb::N_SQUARES * bb::N_SQUARES] = {{{0}}};
// counter move history table (prev_piece, prev_to, side, move_piece, move_to)
int cmh[bb::N_PIECE_TYPES * bb::N_SQUARES][bb::N_COLORS][bb::N_PIECE_TYPES * bb::N_SQUARES] = {0};
int cmh[bb::N_PIECE_TYPES * bb::N_SQUARES][bb::N_COLORS][bb::N_PIECE_TYPES * bb::N_SQUARES] = {{{0}}};
// followup move history
int fmh[bb::N_PIECE_TYPES * bb::N_SQUARES + 1][bb::N_COLORS][bb::N_PIECE_TYPES * bb::N_SQUARES] = {0};
int fmh[bb::N_PIECE_TYPES * bb::N_SQUARES + 1][bb::N_COLORS][bb::N_PIECE_TYPES * bb::N_SQUARES] = {{{0}}};
// kill table, +2 used to make sure we can always reset +2
move::Move killer[bb::N_COLORS][bb::MAX_INTERNAL_PLY + 2][2] = {0};
move::Move killer[bb::N_COLORS][bb::MAX_INTERNAL_PLY + 2][2] = {{{0}}};
// threat data
int threatCount[bb::MAX_INTERNAL_PLY][bb::N_COLORS] = {0};
int threatCount[bb::MAX_INTERNAL_PLY][bb::N_COLORS] = {{0}};
bb::Square mainThreat[bb::MAX_INTERNAL_PLY] = {0};
// eval history across plies
bb::Score eval[bb::N_COLORS][bb::MAX_INTERNAL_PLY] = {0};
bb::Score eval[bb::N_COLORS][bb::MAX_INTERNAL_PLY] = {{0}};
bool sideToReduce;
bool reduce;
bool targetReached = 1;
Expand Down
4 changes: 1 addition & 3 deletions src_files/makefile
Expand Up @@ -8,9 +8,7 @@ _MAKE := $(MAKE) --no-print-directory -C $(_THIS)
CXX = g++
_LIBS_WL := -Wl,--whole-archive -lpthread -Wl,--no-whole-archive
_LIBS := -pthread
_CXXSRCS := $(_SRC)/*.cpp
_CSRCS := $(_SRC)/syzygy/tbprobe.c
_SRCS := $(_CSRCS) $(_CXXSRCS)
_SRCS := $(_SRC)/*.cpp $(_SRC)/syzygy/tbprobe.cpp

# engine name and version
NAME = Koivisto
Expand Down
7 changes: 3 additions & 4 deletions src_files/newmovegen.cpp
Expand Up @@ -23,10 +23,9 @@ using namespace attacks;
using namespace bb;
using namespace move;


static const int piece_values[6] = {
90, 463, 474, 577, 1359, 0,
};
// static const int piece_values[6] = {
// 90, 463, 474, 577, 1359, 0,
// };

void moveGen::init(SearchData* sd, Board* b, Depth ply, Move hashMove, Move previous, Move followup, int mode, Square threatSquare, U64 checkerSq) {
m_sd = sd;
Expand Down
174 changes: 116 additions & 58 deletions src_files/search.cpp
Expand Up @@ -199,25 +199,38 @@ Move Search::bestMove(Board* b, TimeManager* timeman, int threadId) {
// if there is a dtz move available, do not start any threads or search at all. just do the
// dtz move
Move dtzMove = this->probeDTZ(b);
if (dtzMove != 0)
if (dtzMove != 0 && multiPv == 1)
return dtzMove;

if (polyglot::book.enabled) {
if (polyglot::book.enabled && multiPv == 1) {
Move bookmove = polyglot::book.probe(*b);
if (bookmove)
return bookmove;
}

// Clamp MultiPV to the root move count
MoveList rootMoves;
generatePerftMoves(b, &rootMoves);
multiPv = std::min(multiPv, rootMoves.getSize());

// we need to reset the hash between searches
this->table->incrementAge();

// for each thread, we will reset the thread data like node counts, tablebase hits etc.
for (size_t i = 0; i < tds.size(); i++) {
// reseting the thread data
this->tds[i].threadID = i;
this->tds[i].tbhits = 0;
this->tds[i].nodes = 0;
this->tds[i].seldepth = 0;
this->tds[i].threadID = i;
this->tds[i].tbhits = 0;
this->tds[i].nodes = 0;
this->tds[i].rootMoveCount = rootMoves.getSize();

for (int m = 0; m < rootMoves.getSize(); ++m) {
this->tds[i].rootMoves[m].seldepth = 0;
this->tds[i].rootMoves[m].score = -MAX_MATE_SCORE;
this->tds[i].rootMoves[m].prevScore = -MAX_MATE_SCORE;
this->tds[i].rootMoves[m].pv[0] = rootMoves.getMove(m);
this->tds[i].rootMoves[m].pvLen = 1;
}
}

// we will call this function for the other threads which will skip this part and jump
Expand All @@ -231,6 +244,7 @@ Move Search::bestMove(Board* b, TimeManager* timeman, int threadId) {
ThreadData* td = &this->tds[threadId];
// initialise the score outside the loop tp keep track of it during iterations.
// This is required for aspiration windows
Score topScore = 0;
Score score = 0;
Score prevScore = 0;
// we will create a copy of the board object which will be used during search
Expand All @@ -246,56 +260,93 @@ Move Search::bestMove(Board* b, TimeManager* timeman, int threadId) {
// start the main iterative deepening loop
Depth depth;
for (depth = 1; depth <= maxDepth; depth++) {
for (uint16_t& len : td->pvLen) {
len = 0;
}
// do not use aspiration windows if we are in the first few operations since they will be
// done very quick anyway
if (depth < 6) {
score = this->pvSearch(&searchBoard, -MAX_MATE_SCORE, MAX_MATE_SCORE, depth, 0, td, 0, 2);
prevScore = score;
} else {
// initial window size
Score window = 10;
// lower and upper bounds
Score alpha = score - window;
Score beta = score + window;
Depth sDepth = depth;
// widen the window as long as time is left
while (this->timeManager->isTimeLeft()) {
sDepth = sDepth < depth - 3 ? depth - 3 : sDepth;
score = this->pvSearch(&searchBoard, alpha, beta, sDepth, 0, td, 0, 2);
window += window;
// dont widen the window above a size of 500
if (window > 500)
window = MIN_MATE_SCORE;
// adjust the alpha/beta bound based on which side has failed
if (score >= beta) {
beta += window;
sDepth--;
} else if (score <= alpha) {
beta = (alpha + beta) / 2;
alpha -= window;
} else {
break;
// Keep track of when the timeman stops the search
bool timemanAbort = false;

for (td->pvIdx = 0; td->pvIdx < multiPv; ++td->pvIdx) {
for (uint16_t& len : td->pvLen) {
len = 0;
}
td->seldepth = 0;
// do not use aspiration windows if we are in the first few operations since they will be
// done very quick anyway
if (depth < 6) {
score = this->pvSearch(&searchBoard, -MAX_MATE_SCORE, MAX_MATE_SCORE, depth, 0, td, 0, 2);
prevScore = score;
} else {
score = prevScore = td->rootMoves[td->pvIdx].prevScore;
// initial window size
Score window = 10;
// lower and upper bounds
Score alpha = score - window;
Score beta = score + window;
Depth sDepth = depth;
// widen the window as long as time is left
while (this->timeManager->isTimeLeft()) {
sDepth = sDepth < depth - 3 ? depth - 3 : sDepth;
score = this->pvSearch(&searchBoard, alpha, beta, sDepth, 0, td, 0, 2);
window += window;
// dont widen the window above a size of 500
if (window > 500)
window = MIN_MATE_SCORE;
// adjust the alpha/beta bound based on which side has failed
if (score >= beta) {
beta += window;
sDepth--;
} else if (score <= alpha) {
beta = (alpha + beta) / 2;
alpha -= window;
} else {
break;
}
}
}
}
// compute a score which puts the nodes we spent looking at the best move
// in relation to all the nodes searched so far (only thread local)
int timeManScore = td->searchData.spentEffort[getSquareFrom(td->searchData.bestMove)]
[getSquareTo (td->searchData.bestMove)]
* 100 / td->nodes;
// compute a score which puts the nodes we spent looking at the best move
// in relation to all the nodes searched so far (only thread local)
int timeManScore = td->searchData.spentEffort[getSquareFrom(td->searchData.bestMove)]
[getSquareTo (td->searchData.bestMove)]
* 100 / td->nodes;

int evalScore = prevScore - score;


// Copy the PV line into the corresponding root move slot
RootMove& curRootMove = *std::find(&td->rootMoves[0], &td->rootMoves[td->rootMoveCount], td->pv[0][0]);
std::copy_n(td->pv[0], td->pvLen[0], curRootMove.pv);
curRootMove.pvLen = td->pvLen[0];
curRootMove.score = score;
curRootMove.seldepth = td->seldepth;

// Sort the root move list
std::stable_sort(&td->rootMoves[0], &td->rootMoves[td->rootMoveCount]);

// print the info string if its the main thread, don't do partial multipv
// updates when elapsed time is low to avoid cluttering stdout
if (threadId == 0 && (td->pvIdx + 1 == multiPv || (depth > 1 && this->timeManager->elapsedTime() >= 3000))) {
for (int pvLine = 0; pvLine < td->pvIdx + 1; ++pvLine) {
this->printInfoString(depth, td->rootMoves[pvLine].seldepth, td->rootMoves[pvLine].score, td->rootMoves[pvLine].pv, td->rootMoves[pvLine].pvLen, pvLine);
}
for (int pvLine = td->pvIdx + 1; pvLine < multiPv; ++pvLine) {
this->printInfoString(depth - 1, td->rootMoves[pvLine].seldepth, td->rootMoves[pvLine].prevScore, td->rootMoves[pvLine].pv, td->rootMoves[pvLine].pvLen, pvLine);
}
if (td->pvIdx == 0)
topScore = score;
}

int evalScore = prevScore - score;

// print the info string if its the main thread
if (threadId == 0) {
this->printInfoString(depth, score, td->pv[0], td->pvLen[0]);
// if the search finished due to timeout, we also need to stop here
if (!this->timeManager->rootTimeLeft(timeManScore, evalScore)) {
timemanAbort = true;
break;
}
}

// if the search finished due to timeout, we also need to stop here
if (!this->timeManager->rootTimeLeft(timeManScore, evalScore))
// Update the prevScore of each rootMove, and reset the score
for (RootMove& rootMove: td->rootMoves) {
rootMove.prevScore = rootMove.score;
rootMove.score = -MAX_MATE_SCORE;
}
// Quit the loop if the timeman stopped the search
if (timemanAbort)
break;
}

Expand All @@ -314,7 +365,7 @@ Move Search::bestMove(Board* b, TimeManager* timeman, int threadId) {
// collect some information which can be used for benching
this->searchOverview.nodes = this->totalNodes();
this->searchOverview.depth = depth;
this->searchOverview.score = score;
this->searchOverview.score = topScore;
this->searchOverview.time = timeman->elapsedTime();
this->searchOverview.move = best;

Expand Down Expand Up @@ -378,7 +429,7 @@ Score Search::pvSearch(Board* b, Score alpha, Score beta, Depth depth, Depth ply
if (b->getCurrent50MoveRuleCount() >= 50 && b->isInCheck(b->getActivePlayer())) {
MoveList mv {};
generatePerftMoves(b, &mv);
for (size_t i = 0; i < mv.getSize(); i++) {
for (int i = 0; i < mv.getSize(); i++) {
if (b->isLegal(mv.getMove(i)))
return 8 - (td->nodes & MASK<4>);
}
Expand Down Expand Up @@ -640,6 +691,10 @@ Score Search::pvSearch(Board* b, Score alpha, Score beta, Depth depth, Depth ply
if (sameMove(m, skipMove))
continue;

// in multipv mode, exclude root moves already analysed from the search
if (ply == 0 && std::find(&td->rootMoves[0], &td->rootMoves[td->pvIdx], m) != &td->rootMoves[td->pvIdx])
continue ;

if (pv && td->threadID == 0)
td->pvLen[ply + 1] = 0;

Expand Down Expand Up @@ -1138,26 +1193,29 @@ void Search::setHashSize(int hashSize) {
if (table)
table->setSize(hashSize);
}
void Search::setMultiPv(int multiPvCount) {
this->multiPv = multiPvCount;
}
void Search::stop() {
if (timeManager)
timeManager->stopSearch();
}
void Search::printInfoString(Depth depth, Score score, Move* pv, uint16_t pvLen) {
void Search::printInfoString(Depth depth, int sel_depth, Score score, Move* pv, uint16_t pvLen, int pvIdx) {

if (!printInfo)
return;

// extract nodes, seldepth and nps
U64 nodes = totalNodes();
U64 sel_depth = selDepth();
U64 tb_hits = tbHits();
U64 nps = static_cast<U64>(nodes * 1000) /
static_cast<U64>(timeManager->elapsedTime() + 1);

// print basic info string including depth and seldepth
// print basic info string including depth, seldepth and multiPv
std::cout << "info"
<< " depth " << static_cast<int>(depth)
<< " seldepth " << static_cast<int>(sel_depth);
<< " seldepth " << sel_depth
<< " multipv " << (pvIdx + 1);

// print the score. if its a mate score, show mate xx instead of cp xx
if (abs(score) > MIN_MATE_SCORE) {
Expand Down Expand Up @@ -1347,4 +1405,4 @@ Move Search::probeDTZ(Board* board) {
}

ThreadData::ThreadData(int threadId) : threadID(threadId) {}
ThreadData::ThreadData() {}
ThreadData::ThreadData() {}
24 changes: 23 additions & 1 deletion src_files/search.h
Expand Up @@ -39,6 +39,23 @@
#include <vector>

#define MAX_THREADS 256
#define MAX_MULTIPV 256

struct RootMove {
int seldepth;
bb::Score score;
bb::Score prevScore;
move::Move pv[bb::MAX_INTERNAL_PLY + 1];
uint16_t pvLen;

inline bool operator==(const move::Move& m) const {
return move::sameMove(pv[0], m);
}
inline bool operator <(const RootMove& other) const {
return other.score != score ? other.score < score
: other.prevScore < prevScore;
}
};

/**
* data about each thread
Expand All @@ -49,10 +66,13 @@ struct ThreadData {
int seldepth = 0;
int tbhits = 0;
bool dropOut = false;
int pvIdx = 0;
SearchData searchData {};
moveGen generators[bb::MAX_INTERNAL_PLY] {};
move::Move pv[bb::MAX_INTERNAL_PLY + 1][bb::MAX_INTERNAL_PLY + 1] {};
uint16_t pvLen[bb::MAX_INTERNAL_PLY + 1];
RootMove rootMoves[256];
uint16_t rootMoveCount;

ThreadData();

Expand All @@ -72,6 +92,7 @@ struct SearchOverview {

class Search {
int threadCount = 1;
int multiPv = 1;
TranspositionTable* table;
SearchOverview searchOverview;

Expand Down Expand Up @@ -102,9 +123,10 @@ class Search {
void clearHash();
void setThreads(int threads);
void setHashSize(int hashSize);
void setMultiPv(int multiPvCount);
void stop();

void printInfoString(bb::Depth depth, bb::Score score, move::Move* pv, uint16_t pvLen);
void printInfoString(bb::Depth depth, int sel_depth, bb::Score score, move::Move* pv, uint16_t pvLen,int pvIdx);

// basic move functions
move::Move bestMove(Board* b, TimeManager* timeManager, int threadId = 0);
Expand Down
File renamed without changes.
4 changes: 4 additions & 0 deletions src_files/uci.cpp
Expand Up @@ -120,6 +120,7 @@ void uci::uci() {
std::cout << "id author K. Kahre, F. Eggers" << std::endl;
std::cout << "option name Hash type spin default 16 min 1 max " << maxTTSize() << std::endl;
std::cout << "option name Threads type spin default 1 min 1 max " << MAX_THREADS << std::endl;
std::cout << "option name MultiPV type spin default 1 min 1 max " << MAX_MULTIPV << std::endl;
std::cout << "option name OwnBook type check default false" << std::endl;
std::cout << "option name BookPath type string" << std::endl;
std::cout << "option name SyzygyPath type string default" << std::endl;
Expand Down Expand Up @@ -333,6 +334,9 @@ void uci::set_option(const std::string& name, const std::string& value) {
} else if (name == "Threads") {
int count = stoi(value);
searchObject.setThreads(count);
} else if (name == "MultiPV") {
int count = stoi(value);
searchObject.setMultiPv(count);
} else if (name == "OwnBook") {
polyglot::book.enabled = (value == "true");
} else if (name == "BookPath") {
Expand Down

0 comments on commit df558f8

Please sign in to comment.