/*
Texel - A UCI chess engine.
Copyright (C) 2012-2013 Peter Ă–sterlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* treeLogger.hpp
*
* Created on: Feb 25, 2012
* Author: petero
*/
#ifndef TREELOGGER_HPP_
#define TREELOGGER_HPP_
#include "util/util.hpp"
#include "move.hpp"
#include "parallel.hpp"
#include <vector>
#include <type_traits>
#include <utility>
#include <cstring>
#include <fstream>
class TreeLoggerWriter;
class TreeLoggerWriterDummy;
/** Change to TreeLoggerWriter to enable tree logging. */
using TreeLogger = TreeLoggerWriterDummy;
class Position;
namespace Serializer {
template <typename Type>
typename std::enable_if<std::is_integral<Type>::value, U8*>::type
putBytes(U8* buffer, Type value) {
int s = sizeof(value);
memcpy(buffer, &value, s);
return buffer + s;
}
template <int N>
static U8* serialize(U8* buffer) {
static_assert(N >= 0, "Buffer too small");
return buffer;
}
/** Store a sequence of values in a byte buffer. */
template <int N, typename Type0, typename... Types>
static U8* serialize(U8* buffer, Type0 value0, Types... values) {
const int s = sizeof(Type0);
buffer = putBytes(buffer, value0);
return serialize<N-s>(buffer, values...);
}
template <typename Type>
typename std::enable_if<std::is_integral<Type>::value, const U8*>::type
getBytes(const U8* buffer, Type& value) {
int s = sizeof(value);
memcpy(&value, buffer, s);
return buffer + s;
}
template <int N>
static const U8* deSerialize(const U8* buffer) {
static_assert(N >= 0, "Buffer too small");
return buffer;
}
/** Retrieve a sequence of values from a byte buffer. */
template <int N, typename Type0, typename... Types>
static const U8* deSerialize(const U8* buffer, Type0& value0, Types&... values) {
const int s = sizeof(Type0);
buffer = getBytes(buffer, value0);
return deSerialize<N-s>(buffer, values...);
}
}
/** Base class for logging search trees to file. */
class TreeLoggerBase {
friend class TreeLoggerTest;
protected:
/**
* Rules for the log file format
* - A log file contains a search tree dump for one thread.
* - The file contains information for 0 or more searches.
* - The log file for thread 0 contains information for a single search.
* - Information for one search tree starts with a Position0 record and
* ends at the next Position0 record or at the end of the file.
* - A start entry may not have a corresponding end entry. This happens
* if the search was interrupted.
* - Start and end entries are properly nested (assuming the end entries exist)
* s1.index < s2.index => e1.index > e2.index
*/
enum class EntryType : U8 {
POSITION_INCOMPLETE, // First entry in file has this type when endIndex
// has not yet been computed for all StartEntries.
POSITION_PART0, // Position entry, first part.
POSITION_PART1, // Position entry, second part.
POSITION_PART2, // Position entry, third part.
NODE_START, // Start of a search node.
NODE_END // End of a search node.
};
static const U32 endMark = 0xffffffff;
struct Position0 {
U32 nextIndex; // Index of next position, or endMark for last position.
U64 word0;
U64 word1;
template <int N> U8* serialize(U8 buffer[N]) const {
return Serializer::serialize<N>(buffer, nextIndex, word0, word1);
}
template <int N> void deSerialize(const U8 buffer[N]) {
Serializer::deSerialize<N>(buffer, nextIndex, word0, word1);
}
};
struct Position1 {
U32 t0Index;
U64 word2;
U64 word3;
template <int N> U8* serialize(U8 buffer[N]) const {
return Serializer::serialize<N>(buffer, t0Index, word2, word3);
}
template <int N> void deSerialize(const U8 buffer[N]) {
Serializer::deSerialize<N>(buffer, t0Index, word2, word3);
}
};
struct Position2 {
U64 word4;
U8 owningThread;
U8 moveNo;
U32 parentIndex; // Index in owning threads tree log
template <int N> U8* serialize(U8 buffer[N]) const {
return Serializer::serialize<N>(buffer, word4, owningThread, moveNo, parentIndex);
}
template <int N> void deSerialize(const U8 buffer[N]) {
Serializer::deSerialize<N>(buffer, word4, owningThread, moveNo, parentIndex);
}
};
struct StartEntry {
U32 endIndex;
U32 parentIndex; // Points to NODE_START or POSITION_PART0 node.
U16 move;
S16 alpha;
S16 beta;
U8 ply;
U16 depth;
U32 t0Index; // Current entry in thread 0
Move getMove() const {
Move ret;
ret.setMove(move & 63, (move >> 6) & 63, (move >> 12) & 15, 0);
return ret;
}
template <int N> U8* serialize(U8 buffer[N]) const {
return Serializer::serialize<N>(buffer, endIndex, parentIndex, move,
alpha, beta, ply, depth, t0Index);
}
template <int N> void deSerialize(const U8 buffer[N]) {
Serializer::deSerialize<N>(buffer, endIndex, parentIndex, move,
alpha, beta, ply, depth, t0Index);
}
};
struct EndEntry {
U32 startIndex;
S16 score;
U8 scoreType;
S16 evalScore;
U64 hashKey;
U32 t0Index; // Current entry in thread 0
template <int N> U8* serialize(U8 buffer[N]) const {
return Serializer::serialize<N>(buffer, startIndex, score, scoreType,
evalScore, hashKey, t0Index);
}
template <int N> void deSerialize(const U8 buffer[N]) {
Serializer::deSerialize<N>(buffer, startIndex, score, scoreType,
evalScore, hashKey, t0Index);
}
};
struct Entry {
EntryType type;
union {
Position0 p0;
Position1 p1;
Position2 p2;
StartEntry se;
EndEntry ee;
};
static const int bufSize = 22;
using Buffer = U8[bufSize];
void serialize(U8 buffer[bufSize]) const {
U8* ptr = buffer;
using UType = std::underlying_type<EntryType>::type;
const int su = sizeof(UType);
UType uType = static_cast<UType>(type);
ptr = Serializer::serialize<bufSize>(ptr, uType);
switch (type) {
case EntryType::POSITION_INCOMPLETE: p0.serialize<bufSize-su>(ptr); break;
case EntryType::POSITION_PART0: p0.serialize<bufSize-su>(ptr); break;
case EntryType::POSITION_PART1: p1.serialize<bufSize-su>(ptr); break;
case EntryType::POSITION_PART2: p2.serialize<bufSize-su>(ptr); break;
case EntryType::NODE_START: se.serialize<bufSize-su>(ptr); break;
case EntryType::NODE_END: ee.serialize<bufSize-su>(ptr); break;
}
}
void deSerialize(U8 buffer[bufSize]) {
const U8* ptr = buffer;
using UType = std::underlying_type<EntryType>::type;
const int su = sizeof(UType);
UType uType;
ptr = Serializer::deSerialize<bufSize>(ptr, uType);
type = static_cast<EntryType>(uType);
switch (type) {
case EntryType::POSITION_INCOMPLETE: p0.deSerialize<bufSize-su>(ptr); break;
case EntryType::POSITION_PART0: p0.deSerialize<bufSize-su>(ptr); break;
case EntryType::POSITION_PART1: p1.deSerialize<bufSize-su>(ptr); break;
case EntryType::POSITION_PART2: p2.deSerialize<bufSize-su>(ptr); break;
case EntryType::NODE_START: se.deSerialize<bufSize-su>(ptr); break;
case EntryType::NODE_END: ee.deSerialize<bufSize-su>(ptr); break;
}
}
};
Entry entry;
U8 entryBuffer[Entry::bufSize];
};
/** Writer class for logging search trees to file. */
class TreeLoggerWriter : public TreeLoggerBase {
public:
/** Constructor. */
TreeLoggerWriter();
/** Destructor. */
~TreeLoggerWriter();
/** Open log file for writing. */
void open(const std::string& filename, ParallelData& pd, int threadNo);
/** Flush write cache and close log file. */
void close();
/** Return true if log file is opened. */
bool isOpened() const;
/** Log information for new position to search.
* Return index of position entry. */
U64 logPosition(const Position& pos, int owningThread, U64 parentIndex, int moveNo);
/**
* Log information when entering a search node.
* @param parentId Index of parent node.
* @param m Move made to go from parent node to this node
* @param alpha Search parameter
* @param beta Search parameter
* @param ply Search parameter
* @param depth Search parameter
* @return node index
*/
U64 logNodeStart(U64 parentIndex, const Move& m, int alpha, int beta, int ply, int depth);
/**
* Log information when leaving a search node.
* @param startIndex Pointer to corresponding start node entry.
* @param score Computed score for this node.
* @param scoreType See TranspositionTable, T_EXACT, T_GE, T_LE.
* @param evalScore Score returned by evaluation function at this node, if known.
* @return node index
*/
U64 logNodeEnd(U64 startIndex, int score, int scoreType, int evalScore, U64 hashKey);
private:
/** Write position entries to end of file. */
void writePosition(const Position& pos, int owningThread, U64 parentIndex, int moveNo);
/** Write entry to end of file. Uses internal buffering, flushed in close(). */
void appendEntry(const Entry& entry);
bool opened;
std::ofstream os;
U64 nextIndex;
ParallelData* pd;
int threadNo;
static const int writeCacheSize = 1024;
U8 writeCache[Entry::bufSize * writeCacheSize];
int nInWriteCache;
};
/** Dummy version of TreeLoggerWriter. */
class TreeLoggerWriterDummy {
public:
TreeLoggerWriterDummy() { }
void open(const std::string& filename, ParallelData& pd, int threadNo) { }
void close() { }
bool isOpened() const { return false; }
U64 logPosition(const Position& pos, int owningThread, U64 parentIndex, int moveNo) { return 0; }
U64 logNodeStart(U64 parentIndex, const Move& m, int alpha, int beta, int ply, int depth) { return 0; }
U64 logNodeEnd(U64 startIndex, int score, int scoreType, int evalScore, U64 hashKey) { return 0; }
};
/**
* Reader/analysis class for a search tree dumped to a file.
*/
class TreeLoggerReader : public TreeLoggerBase {
public:
/** Constructor. */
TreeLoggerReader(const std::string& filename);
void close();
/** Main loop of the interactive tree browser. */
static void main(const std::string& filename);
private:
/** Compute endIndex for all StartNode entries. */
void computeForwardPointers();
/** Write forward pointer data to disk. */
void flushForwardPointerData(std::vector<std::pair<U64,U64>>& toWrite);
/** Get root node information. */
void getRootNode(U64 index, Position& pos);
void getRootNode(U64 index, Position& pos, int& owningThread,
U64& parentIndex, int& moveNo, U64& t0Index);
/** Read an entry. */
void readEntry(U64 index, Entry& entry);
/** Write an entry. */
void writeEntry(U64 index, const Entry& entry);
/** Run the interactive analysis main loop. */
void mainLoop();
bool isMove(std::string cmdStr) const;
/** Return all nodes with a given hash key. */
void getNodesForHashKey(U64 hashKey, std::vector<U64>& nodes, U64 maxEntry);
/** Get hash key from an input string. */
U64 getHashKey(std::string& s, U64 defKey) const;
/** Get integer parameter from an input string. */
static int getArg(const std::string& s, int defVal);
/** Get a list of integer parameters from an input string. */
void getArgs(const std::string& s, int defVal, std::vector<int>& args);
/** Get a string parameter from an input string. */
static std::string getArgStr(const std::string& s, const std::string& defVal);
void printHelp();
/** Read start/end entries for a tree node. Return true if the end entry exists. */
bool readEntries(int index, StartEntry& se, EndEntry& ee);
/** Find the parent node to a node. */
S64 findParent(S64 index);
/** Find all children of a node. */
void findChildren(S64 index, std::vector<U64>& childs);
/** Get node position in parents children list. */
int getChildNo(U64 index);
/** Get list of nodes from root position to a node. */
void getNodeSequence(U64 index, std::vector<U64>& nodes);
/** Find list of moves from root node to a node.
* Return root node index. */
U64 getMoveSequence(U64 index, std::vector<Move>& moves);
/** Find the position corresponding to a node. */
Position getPosition(U64 index);
void printNodeInfo(U64 index, int childNo = -1, const std::string& filterMove = "");
std::fstream fs;
S64 filePos; // Current file read position (seekg)
U64 numEntries;
};
inline
TreeLoggerWriter::TreeLoggerWriter()
: opened(false), nextIndex(0), pd(nullptr), threadNo(-1), nInWriteCache(0) {
}
inline
TreeLoggerWriter::~TreeLoggerWriter() {
close();
}
inline bool
TreeLoggerWriter::isOpened() const {
return opened;
}
inline U64
TreeLoggerWriter::logPosition(const Position& pos, int owningThread, U64 parentIndex, int moveNo) {
U64 ret = nextIndex;
if (threadNo == 0)
pd->t0Index = (U32)ret;
writePosition(pos, owningThread, parentIndex, moveNo);
return ret;
}
inline U64
TreeLoggerWriter::logNodeStart(U64 parentIndex, const Move& m, int alpha, int beta, int ply, int depth) {
if (!opened)
return 0;
if (threadNo == 0)
pd->t0Index = (U32)nextIndex;
entry.type = EntryType::NODE_START;
entry.se.endIndex = -1;
entry.se.parentIndex = (U32)parentIndex;
entry.se.move = m.from() + (m.to() << 6) + (m.promoteTo() << 12);
entry.se.alpha = alpha;
entry.se.beta = beta;
entry.se.ply = ply;
entry.se.depth = depth;
entry.se.t0Index = pd->t0Index;
appendEntry(entry);
return nextIndex++;
}
inline U64
TreeLoggerWriter::logNodeEnd(U64 startIndex, int score, int scoreType, int evalScore, U64 hashKey) {
if (!opened)
return 0;
if (threadNo == 0)
pd->t0Index = (U32)nextIndex;
entry.type = EntryType::NODE_END;
entry.ee.startIndex = (U32)startIndex;
entry.ee.score = score;
entry.ee.scoreType = scoreType;
entry.ee.evalScore = evalScore;
entry.ee.hashKey = hashKey;
entry.ee.t0Index = pd->t0Index;
appendEntry(entry);
return nextIndex++;
}
inline void
TreeLoggerReader::getRootNode(U64 index, Position& pos) {
int owningThread;
U64 parentIndex;
int moveNo;
U64 t0Index;
getRootNode(index, pos, owningThread, parentIndex, moveNo, t0Index);
}
#endif /* TREELOGGER_HPP_ */