/*
 
    Texel - A UCI chess engine.
 
    Copyright (C) 2013-2014  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/>.
 
*/
 
 
 
/*
 
 * parallel.hpp
 
 *
 
 *  Created on: Jul 9, 2013
 
 *      Author: petero
 
 */
 
 
 
#ifndef PARALLEL_HPP_
 
#define PARALLEL_HPP_
 
 
 
#include "killerTable.hpp"
 
#include "history.hpp"
 
#include "transpositionTable.hpp"
 
#include "evaluate.hpp"
 
#include "searchUtil.hpp"
 
#include "constants.hpp"
 
#include "util/timeUtil.hpp"
 
#include "util/heap.hpp"
 
 
 
#include <memory>
 
#include <vector>
 
#include <set>
 
#include <thread>
 
#include <mutex>
 
#include <condition_variable>
 
#include <atomic>
 
 
 
 
 
class Search;
 
class SearchTreeInfo;
 
 
 
class ParallelData;
 
class SplitPoint;
 
class SplitPointMove;
 
class FailHighInfo;
 
class DepthNpsInfo;
 
 
 
 
 
class WorkerThread {
 
public:
 
    /** Constructor. Does not start new thread. */
 
    WorkerThread(int threadNo, ParallelData& pd, TranspositionTable& tt);
 
 
 
    /** Destructor. Waits for thread to terminate. */
 
    ~WorkerThread();
 
 
 
    WorkerThread(const WorkerThread&) = delete;
 
    WorkerThread& operator=(const WorkerThread&) = delete;
 
 
 
    /** Start thread. */
 
    void start();
 
 
 
    /** Wait for thread to stop. */
 
    void join();
 
 
 
    /** Return true if thread is running. */
 
    bool threadRunning() const;
 
 
 
    /** Return thread number. The first worker thread is number 1. */
 
    int getThreadNo() const;
 
 
 
    /** For debugging. */
 
    double getPUseful() const;
 
 
 
private:
 
    /** Thread main loop. */
 
    void mainLoop(int minProbeDepth);
 
 
 
    int threadNo;
 
    ParallelData& pd;
 
    std::shared_ptr<std::thread> thread;
 
 
 
    std::shared_ptr<Evaluate::EvalHashTables> et;
 
    std::shared_ptr<KillerTable> kt;
 
    std::shared_ptr<History> ht;
 
    TranspositionTable& tt;
 
 
 
    double pUseful; // Probability that thread is currently doing something useful, for debugging
 
};
 
 
 
 
 
/** Priority queue of pending search tasks. Handles thread safety. */
 
class WorkQueue {
 
    friend class ParallelTest;
 
public:
 
    /** Constructor. */
 
    WorkQueue(const FailHighInfo& fhInfo, const DepthNpsInfo& npsInfo);
 
 
 
    /** Set/get stopped flag. */
 
    void setStopped(bool stop);
 
    bool isStopped() const;
 
 
 
    /** Reset dynamic minimum split depth to default value. */
 
    void resetSplitDepth();
 
 
 
    /** Add SplitPoint to work queue. */
 
    void addWork(const std::shared_ptr<SplitPoint>& sp);
 
 
 
    /** Try to add SplitPoint to work queue. If queue is contended add SplitPoint
 
     * to pending instead. */
 
    void tryAddWork(const std::shared_ptr<SplitPoint>& sp,
 
                    std::vector<std::shared_ptr<SplitPoint>>& pending);
 
 
 
    /** Get best move for helper thread to work on. */
 
    std::shared_ptr<SplitPoint> getWork(int& moveNo, ParallelData& pd, int threadNo);
 
    std::shared_ptr<SplitPoint> getWork(int& moveNo, ParallelData& pd, int threadNo,
 
                                        int& prio, double& pUseful);
 
 
 
    /** A helper thread stopped working on a move before it was finished. */
 
    void returnMove(const std::shared_ptr<SplitPoint>& sp, int moveNo);
 
 
 
    /** Set which move number the SplitPoint owner is currently searching.
 
     * @return Score from helper search thread. UNKNOWN_SCORE if no helper has
 
     *         searched the move. */
 
    int setOwnerCurrMove(const std::shared_ptr<SplitPoint>& sp, int moveNo, int alpha);
 
 
 
    /** Cancel this SplitPoint and all children. */
 
    void cancel(const std::shared_ptr<SplitPoint>& sp);
 
 
 
    /** Set move to canceled after helper thread finished searching it. */
 
    void moveFinished(const std::shared_ptr<SplitPoint>& sp, int moveNo,
 
                      bool cancelRemaining, int score);
 
 
 
    /** Return probability that the best unstarted move needs to be searched.
 
     *  Also return the corresponding SplitPoint. */
 
    double getBestProbability(std::shared_ptr<SplitPoint>& bestSp) const;
 
    double getBestProbability() const;
 
    int getBestPrio() const;
 
 
 
    /** Return current dynamic minimum split depth. */
 
    int getMinSplitDepth() const;
 
 
 
    /** For performance measurements on queue operations. */
 
    void resetStat();
 
    TimeSampleStatistics& getAddWorkStat(int th);
 
    TimeSampleStatistics& getGetWorkStat(int th);
 
    void printStats(std::ostream& os, int nThreads);
 
 
 
private:
 
    /** Insert sp in queue. Notify condition variable if queue becomes non-empty. */
 
    void insertInQueue(const std::shared_ptr<SplitPoint>& sp);
 
 
 
    /** Recompute probabilities for "sp" and all children. Update "queue" and "waiting". */
 
    void updateProbabilities(const std::shared_ptr<SplitPoint>& sp);
 
 
 
    /** Cancel "sp" and all children. Assumes mutex already locked. */
 
    void cancelInternal(const std::shared_ptr<SplitPoint>& sp);
 
 
 
    void printSpTree(std::ostream& os, const ParallelData& pd,
 
                     int threadNo, const std::shared_ptr<SplitPoint> selectedSp,
 
                     int selectedMove);
 
    void findLeaves(const std::shared_ptr<SplitPoint>& sp, std::vector<int>& parentThreads,
 
                    std::vector<std::shared_ptr<SplitPoint>>& leaves);
 
 
 
    /** Helper for addWork() and tryAddWork(). */
 
    void addWorkInternal(const std::shared_ptr<SplitPoint>& sp);
 
 
 
    /** Scoped lock that measures lock contention and adjusts minSplitDepth accordingly. */
 
    class Lock {
 
    public:
 
        Lock(const WorkQueue* wq0);
 
        void wait(std::condition_variable& cv);
 
    private:
 
        const WorkQueue& wq;
 
        std::unique_lock<std::mutex> lock;
 
    };
 
    friend class Lock;
 
    std::atomic<bool> stopped;
 
 
 
    mutable int minSplitDepth;      // Dynamic minimum split depth
 
    mutable U64 nContended;         // Number of times mutex has been contended
 
    mutable U64 nNonContended;      // Number of times mutex has not been contended
 
 
 
    std::condition_variable cv;     // Notified when wq becomes non-empty and when search should stop
 
    const FailHighInfo& fhInfo;
 
    const DepthNpsInfo& npsInfo;
 
    mutable std::mutex mutex;
 
 
 
    // SplitPoints with unstarted SplitPointMoves
 
    // SplitPoints with no unstarted moves have negative priority.
 
    Heap<SplitPoint> queue;
 
 
 
    // For performance debugging
 
    static const int maxStatThreads = 64;
 
    TimeSampleStatisticsVector<maxStatThreads*2> wqStat;
 
};
 
 
 
 
 
/** Fail high statistics, for estimating SplitPoint usefulness probabilities. */
 
class FailHighInfo {
 
public:
 
    /** Constructor. */
 
    FailHighInfo();
 
 
 
    /** Return probability that move moveNo needs to be searched.
 
     * @param parentMoveNo  Move number of move leading to this position.
 
     * @param currMoveNo    Move currently being searched.
 
     * @param moveNo        Move number to get probability for.
 
     * @param allNode       True if this is an expected ALL node. */
 
    double getMoveNeededProbability(int parentMoveNo, int currMoveNo, int moveNo, bool allNode) const;
 
 
 
    /** Return probability that move moveNo needs to be searched in a PV node.
 
     * @param currMoveNo    Move currently being searched.
 
     * @param moveNo        Move number to get probability for.
 
     * @param allNode       True if this is an expected ALL node. */
 
    double getMoveNeededProbabilityPv(int currMoveNo, int moveNo) const;
 
 
 
    /** Add fail high information.
 
     * @param parentMoveNo  Move number of move leading to this position.
 
     * @param nSearched     Number of moves searched at this node.
 
     * @param failHigh      True if the node failed high.
 
     * @param allNode       True if this is an expected ALL node. */
 
    void addData(int parentMoveNo, int nSearched, bool failHigh, bool allNode);
 
 
 
    /** Add "alpha increased" information for PV nodes.
 
     * @param  nSearched     Number of moves searched at this node.
 
     * @param  alphaChanged  True if alpha increased after searching move. */
 
    void addPvData(int nSearched, bool alphaChanged);
 
 
 
    /** Rescale the counters so that future updates have more weight. */
 
    void reScale();
 
 
 
    /** Print object state to "os", for debugging. */
 
    void print(std::ostream& os) const;
 
 
 
private:
 
    void reScaleInternal(int factor);
 
    void reScalePv(int factor);
 
 
 
    int getNodeType(int moveNo, bool allNode) const;
 
 
 
    mutable std::mutex mutex;
 
 
 
    static const int NUM_NODE_TYPES = 4;
 
    static const int NUM_STAT_MOVES = 15;
 
 
 
    RangeSumArray<NUM_STAT_MOVES> failHiCount[NUM_NODE_TYPES]; // [parentMoveNo>0?1:0][moveNo]
 
    std::atomic<int> failLoCount[NUM_NODE_TYPES];              // [parentMoveNo>0?1:0]
 
    std::atomic<int> totCount;                                 // Sum of all counts
 
 
 
    std::atomic<int> newAlpha[NUM_STAT_MOVES];
 
    std::atomic<int> totPvCount;
 
};
 
 
 
class DepthNpsInfo {
 
public:
 
    /** Constructor. */
 
    DepthNpsInfo();
 
 
 
    /** Reset */
 
    void reset();
 
 
 
    /** Set thread 0 estimated nps. Used to scale efficiency calculations
 
     *  to [0,1] interval. */
 
    void setBaseNps(double nps);
 
 
 
    /** Add one data point. */
 
    void addData(int depth, U64 nodes, double waitTime, double searchTime);
 
 
 
    /** Return estimate search efficiency when searching to a given depth. */
 
    double efficiency(int depth) const;
 
 
 
    /** Print object state to "os", for debugging. */
 
    void print(std::ostream& os, int iterDepth);
 
 
 
    /** Maximum depth statistics is kept for. */
 
    static const int maxDepth = 48;
 
 
 
private:
 
    /** Helper method for efficiency() and print(). */
 
    double efficiencyInternal(int depth) const;
 
 
 
    mutable std::mutex mutex;
 
 
 
    struct NpsData {
 
        NpsData();
 
        U32 nSearches;
 
        U64 nodes;
 
        double time;
 
    };
 
    NpsData npsData[maxDepth];
 
    double nps0;
 
    U32 nSearches;      // Total number of searches
 
    double waitTime;    // Total waiting time
 
};
 
 
 
/** Top-level parallel search data structure. */
 
class ParallelData {
 
public:
 
    /** Constructor. */
 
    ParallelData(TranspositionTable& tt);
 
 
 
    /** Create/delete worker threads so that there are numWorkers in total. */
 
    void addRemoveWorkers(int numWorkers);
 
 
 
    /** Start all worker threads. */
 
    void startAll();
 
 
 
    /** Stop all worker threads. */
 
    void stopAll();
 
 
 
 
 
    /** Return number of helper threads in use. */
 
    int numHelperThreads() const;
 
 
 
    /** Get number of nodes searched by all helper threads. */
 
    S64 getNumSearchedNodes() const;
 
 
 
    /** Get number of TB hits for all helper threads. */
 
    S64 getTbHits() const;
 
 
 
    /** Add nNodes to total number of searched nodes. */
 
    void addSearchedNodes(S64 nNodes);
 
 
 
    /** Add nTbHits to number of TB hits. */
 
    void addTbHits(S64 nTbHits);
 
 
 
 
 
    /** For debugging. */
 
    const WorkerThread& getHelperThread(int i) const;
 
 
 
    FailHighInfo fhInfo;
 
    DepthNpsInfo npsInfo;
 
 
 
    WorkQueue wq;
 
 
 
    /** Move played in Search::iterativeDeepening before calling negaScout. For debugging. */
 
    Move topMove;
 
 
 
    /** Current node index in thread 0. Used by tree logging code. */
 
    std::atomic<U32> t0Index;
 
 
 
private:
 
    /** Vector of helper threads. Master thread not included. */
 
    std::vector<std::shared_ptr<WorkerThread>> threads;
 
 
 
    TranspositionTable& tt;
 
 
 
    std::atomic<S64> totalHelperNodes; // Number of nodes searched by all helper threads
 
    std::atomic<S64> helperTbHits;     // Number of TB hits for all helper threads
 
};
 
 
 
 
 
/** SplitPoint does not handle thread safety, WorkQueue must do that.  */
 
class SplitPoint : public Heap<SplitPoint>::HeapObject {
 
    friend class ParallelTest;
 
public:
 
    /** Constructor. */
 
    SplitPoint(int threadNo,
 
               const std::shared_ptr<SplitPoint>& parentSp, int parentMoveNo,
 
               const Position& pos, const std::vector<U64>& posHashList,
 
               int posHashListSize, const SearchTreeInfo& sti,
 
               const KillerTable& kt, const History& ht,
 
               int alpha, int beta, int ply, int depth);
 
 
 
    SplitPoint(const SplitPoint&) = delete;
 
    SplitPoint& operator=(const SplitPoint&) = delete;
 
 
 
    /** Add a child SplitPoint */
 
    void addChild(const std::weak_ptr<SplitPoint>& child);
 
 
 
    /** Add a move to the SplitPoint. */
 
    void addMove(int moveNo, const SplitPointMove& spMove);
 
 
 
    /** Assign sequence number. */
 
    void setSeqNo();
 
 
 
    /** compute pSpUseful and pnextMoveUseful. */
 
    void computeProbabilities(const FailHighInfo& fhInfo, const DepthNpsInfo& npsInfo);
 
 
 
    /** Get parent SplitPoint, or null for root SplitPoint. */
 
    std::shared_ptr<SplitPoint> getParent() const;
 
 
 
    /** Get children SplitPoints. */
 
    const std::vector<std::weak_ptr<SplitPoint>>& getChildren() const;
 
 
 
 
 
    U64 getSeqNo() const;
 
    double getPSpUseful() const;
 
    double getPNextMoveUseful() const;
 
    const History& getHistory() const;
 
    const KillerTable& getKillerTable() const;
 
    const SplitPointMove& getSpMove(int moveNo) const;
 
 
 
    /** Return probability that moveNo needs to be searched. */
 
    double getPMoveUseful(const FailHighInfo& fhInfo, int moveNo) const;
 
 
 
    void getPos(Position& pos, const Move& move) const;
 
    void getPosHashList(const Position& pos, std::vector<U64>& posHashList,
 
                        int& posHashListSize) const;
 
    const SearchTreeInfo& getSearchTreeInfo() const;
 
    int getAlpha() const;
 
    int getBeta() const;
 
    int getPly() const;
 
    int getDepth() const;
 
 
 
 
 
    /** Get index of first unstarted move. Mark move as being searched.
 
     * Return -1 if there are no unstarted moves. */
 
    int getNextMove(const FailHighInfo& fhInfo);
 
 
 
    /** A helper thread stopped working on a move before it was finished. */
 
    void returnMove(int moveNo);
 
 
 
    /** Set which move number the SplitPoint owner is currently searching.
 
     * @return Score from helper search thread. UNKNOWN_SCORE if no helper has
 
     *         searched the move. */
 
    int setOwnerCurrMove(int moveNo, int alpha);
 
 
 
    /** Cancel this SplitPoint and all children. */
 
    void cancel();
 
 
 
    /** Return true if this SplitPoint has been canceled. */
 
    bool isCanceled() const;
 
 
 
    /** Set move to canceled after helper thread finished searching it. */
 
    void moveFinished(int moveNo, bool cancelRemaining, int score);
 
 
 
    /** Return true if there are moves that have not been started to be searched. */
 
    bool hasUnStartedMove() const;
 
 
 
    /** Return true if there are moves that have not been finished (canceled) yet. */
 
    bool hasUnFinishedMove() const;
 
 
 
    /** Return true if this SplitPoint is an ancestor to "sp". */
 
    bool isAncestorTo(const SplitPoint& sp) const;
 
 
 
    /** Return true if some other thread is helping the SplitPoint owner. */
 
    bool hasHelperThread() const;
 
 
 
    /** Return true if the held SplitPoint is an estimated ALL node. */
 
    bool isAllNode() const;
 
 
 
    /** Return true if beta > alpha + 1. */
 
    bool isPvNode() const;
 
 
 
    /** Compute SplitPoint priority. The SplitPoint with highest
 
     * priority will be selected first by the next available thread. */
 
    int getSpPrio(const DepthNpsInfo& npsInfo) const;
 
 
 
    /** Thread that created this SplitPoint. */
 
    int owningThread() const;
 
 
 
    /** Return true if object is or has been inserted in WorkQueue. */
 
    bool wasInserted() const;
 
 
 
    /** Mark SplitPoint as inserted in WorkQueue. */
 
    void setInserted();
 
 
 
    /** Print object state to "os", for debugging. */
 
    void print(std::ostream& os, int level, const FailHighInfo& fhInfo) const;
 
 
 
    /** For debugging. */
 
    int getParentMoveNo() const;
 
 
 
    /** For debugging. */
 
    int getCurrMoveNo() const;
 
 
 
    /** Get index of first unstarted move, or -1 if there is no unstarted move. */
 
    int findNextMove(const FailHighInfo& fhInfo) const;
 
 
 
private:
 
    /** Return probability that moveNo needs to be searched, by calling corresponding
 
     * function in fhInfo. */
 
    double getMoveNeededProbability(const FailHighInfo& fhInfo, int moveNo) const;
 
 
 
    /** Remove null entries from children vector. */
 
    void cleanUpChildren();
 
 
 
    const Position pos;
 
    const std::vector<U64> posHashList; // List of hashes for previous positions up to the last "zeroing" move.
 
    const int posHashListSize;
 
    const SearchTreeInfo searchTreeInfo;   // For ply-1
 
    const KillerTable& kt;
 
    const History& ht;
 
 
 
    RelaxedShared<int> alpha;
 
    const int beta;
 
    const int ply;
 
    const int depth;
 
    const bool isPV;
 
 
 
    double pSpUseful;       // Probability that this SplitPoint is needed. 100% if parent is null.
 
    double pNextMoveUseful; // Probability that next unstarted move needs to be searched.
 
 
 
    const int threadNo;     // Owning thread
 
    const std::shared_ptr<SplitPoint> parent;
 
    const int parentMoveNo; // Move number in parent SplitPoint that generated this SplitPoint
 
    std::vector<std::weak_ptr<SplitPoint>> children;
 
 
 
    static U64 nextSeqNo;
 
    U64 seqNo;      // To break ties when two objects have same priority. Set by addWork() under lock
 
    int currMoveNo;
 
    std::vector<SplitPointMove> spMoves;
 
 
 
    bool inserted; // True if sp has been inserted in WorkQueue. Remains true after deletion.
 
    bool canceled;
 
};
 
 
 
 
 
/** Represents one move at a SplitPoint. */
 
class SplitPointMove {
 
public:
 
    /** Constructor */
 
    SplitPointMove(const Move& move, int lmr, int depth,
 
                   int captSquare, bool inCheck);
 
 
 
    const Move& getMove() const;
 
    int getLMR() const;
 
    int getDepth() const;
 
    int getRecaptureSquare() const;
 
    bool getInCheck() const;
 
 
 
    bool isCanceled() const;
 
    void setCanceled(bool value);
 
 
 
    bool isSearching() const;
 
    void setSearching(bool value);
 
 
 
    void setScore(int s);
 
    int getScore() const;
 
 
 
private:
 
    Move move;      // Position defined by sp->pos + move
 
    int lmr;        // Amount of LMR reduction
 
    int depth;
 
    int captSquare; // Recapture square, or -1 if no recapture
 
    bool inCheck;   // True if side to move is in check
 
 
 
    RelaxedShared<bool> canceled; // Result is no longer needed
 
    bool searching; // True if currently searched by a helper thread
 
    int score;
 
};
 
 
 
 
 
/** Handle a SplitPoint object using RAII. */
 
class SplitPointHolder {
 
public:
 
    /** Constructor. */
 
    SplitPointHolder(ParallelData& pd, std::vector<std::shared_ptr<SplitPoint>>& spVec,
 
                     std::vector<std::shared_ptr<SplitPoint>>& pending);
 
 
 
    /** Destructor. Cancel SplitPoint. */
 
    ~SplitPointHolder();
 
 
 
    SplitPointHolder(const SplitPointHolder&) = delete;
 
    SplitPointHolder& operator=(const SplitPointHolder&) = delete;
 
 
 
    /** Set the SplitPoint object. */
 
    void setSp(const std::shared_ptr<SplitPoint>& sp);
 
 
 
    /** Add a move to the SplitPoint. */
 
    void addMove(int moveNo, const SplitPointMove& spMove);
 
 
 
    /** Add SplitPoint to work queue. If the queue is contended, store SplitPoint
 
     * in pending vector instead. If queue is not contended, also insert all
 
     * objects from the pending vector and clear the pending vector. */
 
    void addToQueue();
 
 
 
    /** Set which move number the SplitPoint owner is currently searching.
 
     * @return Score from helper search thread. UNKNOWN_SCORE if no helper has
 
     *         searched the move. */
 
    int setOwnerCurrMove(int moveNo, int alpha);
 
 
 
    /** For debugging. */
 
    U64 getSeqNo() const;
 
 
 
    /** Return true if the held SplitPoint is an estimated ALL node. */
 
    bool isAllNode() const;
 
 
 
    /** Return true if some other thread is helping the help SplitPoint. */
 
    bool hasHelperThread() const;
 
 
 
private:
 
    ParallelData& pd;
 
    std::vector<std::shared_ptr<SplitPoint>>& spVec;
 
    std::vector<std::shared_ptr<SplitPoint>>& pending;
 
    std::shared_ptr<SplitPoint> sp;
 
    enum class State { EMPTY, CREATED, QUEUED } state;
 
};
 
 
 
/** Dummy version of SplitPointHolder class. */
 
struct DummySplitPointHolder {
 
    DummySplitPointHolder(ParallelData& pd, std::vector<std::shared_ptr<SplitPoint>>& spVec,
 
                          std::vector<std::shared_ptr<SplitPoint>>& pending) {}
 
    void setSp(const std::shared_ptr<SplitPoint>& sp) {}
 
    void addMove(int moveNo, const SplitPointMove& spMove) {}
 
    void addToQueue() {}
 
    int setOwnerCurrMove(int moveNo, int alpha) { return SearchConst::UNKNOWN_SCORE; }
 
    bool isAllNode() const { return false; }
 
};
 
 
 
template <bool smp> struct SplitPointTraits {
 
};
 
template<> struct SplitPointTraits<true> {
 
    using SpHolder = SplitPointHolder;
 
};
 
template<> struct SplitPointTraits<false> {
 
    using SpHolder = DummySplitPointHolder;
 
};
 
 
 
 
 
inline bool
 
WorkerThread::threadRunning() const {
 
    return thread != nullptr;
 
}
 
 
 
inline int
 
WorkerThread::getThreadNo() const {
 
    return threadNo;
 
}
 
 
 
inline double
 
WorkerThread::getPUseful() const {
 
    return pUseful;
 
}
 
 
 
inline
 
WorkQueue::WorkQueue(const FailHighInfo& fhInfo0, const DepthNpsInfo& npsInfo0)
 
    : stopped(false), fhInfo(fhInfo0), npsInfo(npsInfo0) {
 
    resetSplitDepth();
 
}
 
 
 
inline bool
 
WorkQueue::isStopped() const {
 
    return stopped;
 
}
 
 
 
inline void
 
WorkQueue::resetSplitDepth() {
 
    minSplitDepth = SearchConst::MIN_SMP_DEPTH;
 
    nContended = 0;
 
    nNonContended = 0;
 
}
 
 
 
inline int
 
WorkQueue::getMinSplitDepth() const {
 
    return minSplitDepth;
 
}
 
 
 
inline void
 
WorkQueue::insertInQueue(const std::shared_ptr<SplitPoint>& sp) {
 
    bool wasEmpty = queue.empty() || (queue.front()->getPrio() < 0);
 
    queue.insert(sp, sp->getSpPrio(npsInfo));
 
    sp->setInserted();
 
    if (wasEmpty)
 
        cv.notify_all();
 
}
 
 
 
inline void
 
WorkQueue::resetStat() {
 
    for (auto& s : wqStat)
 
        s.reset();
 
}
 
 
 
inline TimeSampleStatistics&
 
WorkQueue::getAddWorkStat(int th) {
 
    assert(th < maxStatThreads);
 
    return wqStat[th];
 
}
 
 
 
inline TimeSampleStatistics&
 
WorkQueue::getGetWorkStat(int th) {
 
    assert(th < maxStatThreads);
 
    return wqStat[maxStatThreads+th];
 
}
 
 
 
inline void
 
WorkQueue::printStats(std::ostream& os, int nThreads) {
 
    for (int i = 0; i < nThreads; i++) {
 
        os << "th:" << i << " add: ";
 
        getAddWorkStat(i).printNs(os);
 
        os << " get: ";
 
        getGetWorkStat(i).printNs(os);
 
        os << std::endl;
 
    }
 
}
 
 
 
 
 
inline int
 
FailHighInfo::getNodeType(int moveNo, bool allNode) const {
 
    if (moveNo == 0)
 
        return allNode ? 0 : 3;
 
    else if (moveNo > 0)
 
        return 1;
 
    else
 
        return 2;
 
}
 
 
 
inline
 
DepthNpsInfo::DepthNpsInfo() {
 
    reset();
 
}
 
 
 
inline
 
DepthNpsInfo::NpsData::NpsData()
 
    : nSearches(0), nodes(0), time(0) {
 
}
 
 
 
inline void
 
DepthNpsInfo::reset() {
 
    std::lock_guard<std::mutex> L(mutex);
 
    for (int i = 0; i < maxDepth; i++) {
 
        npsData[i].nSearches = 0;
 
        npsData[i].nodes = 0;
 
        npsData[i].time = 0;
 
    }
 
    nps0 = 0;
 
    nSearches = 0;
 
    waitTime = 0;
 
}
 
 
 
inline void
 
DepthNpsInfo::setBaseNps(double nps) {
 
    std::lock_guard<std::mutex> L(mutex);
 
    nps0 = nps;
 
}
 
 
 
inline void
 
DepthNpsInfo::addData(int depth, U64 nodes, double wTime, double searchTime) {
 
    if (depth >= maxDepth)
 
        depth = maxDepth - 1;
 
    std::lock_guard<std::mutex> L(mutex);
 
    npsData[depth].nSearches++;
 
    npsData[depth].nodes += nodes;
 
    npsData[depth].time += searchTime;
 
    nSearches++;
 
    waitTime += wTime;
 
}
 
 
 
inline double
 
DepthNpsInfo::efficiency(int depth) const {
 
    if (depth >= maxDepth)
 
        depth = maxDepth - 1;
 
    std::lock_guard<std::mutex> L(mutex);
 
    return efficiencyInternal(depth);
 
}
 
 
 
inline double
 
DepthNpsInfo::efficiencyInternal(int depth) const {
 
    if ((npsData[depth].time > 0) && (nps0 > 0)) {
 
        U64 nodes = npsData[depth].nodes;
 
        double time = npsData[depth].time;
 
        const U32 ns = npsData[depth].nSearches;
 
        if (nSearches > 0)
 
            time += waitTime / nSearches * ns;
 
        double nps = nodes / time;
 
        double eff = nps / nps0;
 
        if (eff > 1.0)
 
            eff = 1.0;
 
        return (eff * ns + 1 * 500) / (ns + 500);
 
    } else
 
        return 1.0;
 
}
 
 
 
inline void
 
WorkQueue::Lock::wait(std::condition_variable& cv) {
 
    cv.wait(lock);
 
}
 
 
 
 
 
inline ParallelData::ParallelData(TranspositionTable& tt0)
 
    : wq(fhInfo, npsInfo), t0Index(0), tt(tt0),
 
      totalHelperNodes(0), helperTbHits(0) {
 
}
 
 
 
inline int
 
ParallelData::numHelperThreads() const {
 
    return (int)threads.size();
 
}
 
 
 
inline S64
 
ParallelData::getNumSearchedNodes() const {
 
    return totalHelperNodes;
 
}
 
 
 
inline S64
 
ParallelData::getTbHits() const {
 
    return helperTbHits;
 
}
 
 
 
inline void
 
ParallelData::addSearchedNodes(S64 nNodes) {
 
    totalHelperNodes += nNodes;
 
}
 
 
 
inline void
 
ParallelData::addTbHits(S64 nTbHits) {
 
    helperTbHits += nTbHits;
 
}
 
 
 
inline const WorkerThread&
 
ParallelData::getHelperThread(int i) const {
 
    return *threads[i];
 
}
 
 
 
 
 
inline bool
 
SplitPoint::isCanceled() const {
 
    return canceled;
 
}
 
 
 
inline void
 
SplitPoint::setSeqNo() {
 
    seqNo = nextSeqNo++;
 
}
 
 
 
inline std::shared_ptr<SplitPoint>
 
SplitPoint::getParent() const {
 
    return parent;
 
}
 
 
 
inline const std::vector<std::weak_ptr<SplitPoint>>&
 
SplitPoint::getChildren() const {
 
    return children;
 
}
 
 
 
inline U64
 
SplitPoint::getSeqNo() const {
 
    return seqNo;
 
}
 
 
 
inline double
 
SplitPoint::getPSpUseful() const {
 
    return pSpUseful;
 
}
 
 
 
inline double
 
SplitPoint::getPNextMoveUseful() const {
 
    return pNextMoveUseful;
 
}
 
 
 
inline const History&
 
SplitPoint::getHistory() const {
 
    return ht;
 
}
 
 
 
inline const KillerTable&
 
SplitPoint::getKillerTable() const {
 
    return kt;
 
}
 
 
 
inline const SplitPointMove&
 
SplitPoint::getSpMove(int moveNo) const {
 
    return spMoves[moveNo];
 
}
 
 
 
inline const SearchTreeInfo&
 
SplitPoint::getSearchTreeInfo() const {
 
    return searchTreeInfo;
 
}
 
 
 
inline int
 
SplitPoint::getAlpha() const {
 
    return alpha;
 
}
 
 
 
inline int
 
SplitPoint::getBeta() const {
 
    return beta;
 
}
 
 
 
inline int
 
SplitPoint::getPly() const {
 
    return ply;
 
}
 
 
 
inline int
 
SplitPoint::getDepth() const {
 
    return depth;
 
}
 
 
 
inline void
 
SplitPoint::returnMove(int moveNo) {
 
    assert((moveNo >= 0) && (moveNo < (int)spMoves.size()));
 
    SplitPointMove& spm = spMoves[moveNo];
 
    spm.setSearching(false);
 
}
 
 
 
inline int
 
SplitPoint::setOwnerCurrMove(int moveNo, int newAlpha) {
 
    assert((moveNo >= 0) && (moveNo < (int)spMoves.size()));
 
    int score = spMoves[moveNo].getScore();
 
    spMoves[moveNo].setCanceled(true);
 
    currMoveNo = moveNo;
 
    if (newAlpha > alpha)
 
        alpha = newAlpha;
 
    return score;
 
}
 
 
 
inline void
 
SplitPoint::cancel() {
 
    canceled = true;
 
    for (SplitPointMove& spMove : spMoves)
 
        spMove.setCanceled(true);
 
}
 
 
 
inline void
 
SplitPoint::addChild(const std::weak_ptr<SplitPoint>& child) {
 
    children.push_back(child);
 
}
 
 
 
inline bool
 
SplitPoint::isPvNode() const {
 
    return isPV;
 
}
 
 
 
inline int
 
SplitPoint::getSpPrio(const DepthNpsInfo& npsInfo) const {
 
    if (!hasUnStartedMove())
 
        return -1;
 
    return (int)(getPNextMoveUseful() * npsInfo.efficiency(getDepth()) * 1000);
 
}
 
 
 
inline int
 
SplitPoint::owningThread() const {
 
    return threadNo;
 
}
 
 
 
inline bool
 
SplitPoint::wasInserted() const {
 
    return inserted;
 
}
 
 
 
inline void
 
SplitPoint::setInserted() {
 
    inserted = true;
 
}
 
 
 
inline int
 
SplitPoint::getParentMoveNo() const {
 
    return parentMoveNo;
 
}
 
 
 
inline int
 
SplitPoint::getCurrMoveNo() const {
 
    return currMoveNo;
 
}
 
 
 
 
 
inline
 
SplitPointMove::SplitPointMove(const Move& move0, int lmr0, int depth0,
 
                              int captSquare0, bool inCheck0)
 
    : move(move0), lmr(lmr0), depth(depth0), captSquare(captSquare0),
 
      inCheck(inCheck0), canceled(false), searching(false),
 
      score(SearchConst::UNKNOWN_SCORE) {
 
}
 
 
 
inline const Move&
 
SplitPointMove::getMove() const {
 
    return move;
 
}
 
 
 
inline int
 
SplitPointMove::getLMR() const {
 
    return lmr;
 
}
 
 
 
inline int
 
SplitPointMove::getDepth() const {
 
    return depth;
 
}
 
 
 
inline int
 
SplitPointMove::getRecaptureSquare() const {
 
    return captSquare;
 
}
 
 
 
inline bool
 
SplitPointMove::getInCheck() const {
 
    return inCheck;
 
}
 
 
 
inline bool
 
SplitPointMove::isCanceled() const {
 
    return canceled;
 
}
 
 
 
inline void
 
SplitPointMove::setCanceled(bool value) {
 
    canceled = value;
 
}
 
 
 
inline bool
 
SplitPointMove::isSearching() const {
 
    return searching;
 
}
 
 
 
inline void
 
SplitPointMove::setSearching(bool value) {
 
    searching = value;
 
}
 
 
 
inline void
 
SplitPointMove::setScore(int s) {
 
    score = s;
 
}
 
 
 
inline int
 
SplitPointMove::getScore() const {
 
    return score;
 
}
 
 
 
 
 
inline
 
SplitPointHolder::SplitPointHolder(ParallelData& pd0,
 
                                   std::vector<std::shared_ptr<SplitPoint>>& spVec0,
 
                                   std::vector<std::shared_ptr<SplitPoint>>& pending0)
 
    : pd(pd0), spVec(spVec0), pending(pending0),
 
      state(State::EMPTY) {
 
}
 
 
 
inline
 
SplitPointHolder::~SplitPointHolder() {
 
    if (state == State::QUEUED) {
 
//        log([&](std::ostream& os){os << "cancel seqNo:" << sp->getSeqNo();});
 
        if (sp->wasInserted())
 
            pd.wq.cancel(sp);
 
        else {
 
            if (pending.size() > 0) {
 
                assert(pending[pending.size()-1] == sp);
 
                pending.pop_back();
 
            }
 
        }
 
        assert(!spVec.empty());
 
        spVec.pop_back();
 
    }
 
}
 
 
 
inline void
 
SplitPointHolder::addMove(int moveNo, const SplitPointMove& spMove) {
 
    assert(state == State::CREATED);
 
    sp->addMove(moveNo, spMove);
 
}
 
 
 
inline int
 
SplitPointHolder::setOwnerCurrMove(int moveNo, int alpha) {
 
//    if (sp->hasHelperThread())
 
//        log([&](std::ostream& os){os << "seqNo:" << sp->getSeqNo() << " currMove:" << moveNo
 
//                                     << " a:" << alpha;});
 
    if (sp->wasInserted())
 
        return pd.wq.setOwnerCurrMove(sp, moveNo, alpha);
 
    else
 
        return sp->setOwnerCurrMove(moveNo, alpha);
 
}
 
 
 
inline U64
 
SplitPointHolder::getSeqNo() const {
 
    return sp->getSeqNo();
 
}
 
 
 
inline bool
 
SplitPointHolder::isAllNode() const {
 
    return sp->isAllNode();
 
}
 
 
 
inline bool
 
SplitPointHolder::hasHelperThread() const {
 
    return sp->hasHelperThread();
 
}
 
 
 
#endif /* PARALLEL_HPP_ */