openingStatistics.cc
Go to the documentation of this file.
00001 #include "osl/move.h"
00002 #include "osl/eval/pieceEval.h"
00003 #include "osl/hash/hashKey.h"
00004 #include "osl/misc/math.h"
00005 #include "osl/record/csa.h"
00006 #include "osl/record/csaRecord.h"
00007 #include "osl/record/kanjiPrint.h"
00008 #include "osl/record/record.h"
00009 #include "osl/record/opening/openingBook.h"
00010 #include "osl/search/fixedEval.h"
00011 #include "osl/search/quiescenceSearch2.h"
00012 #include "osl/search/quiescenceSearch2.tcc"
00013 #include "osl/search/simpleHashTable.h"
00014 #include "osl/state/numEffectState.h"
00015 #include "osl/stl/vector.h"
00016 #include <boost/format.hpp>
00017 #include <boost/lambda/lambda.hpp>
00018 #include <boost/lambda/bind.hpp>
00019 #include <boost/program_options.hpp>
00020 #include <boost/progress.hpp>
00021 #include <boost/shared_ptr.hpp>
00022 #include <deque>
00023 #include <iostream>
00024 #include <vector>
00025 
00026 
00027 using namespace boost::lambda;
00028 namespace bp = boost::program_options;
00029 bp::variables_map vm;
00030 
00031 typedef std::vector<osl::record::opening::WMove> WMoveContainer;
00032 
00033 osl::Player the_player = osl::BLACK;
00034 bool is_dump = false;
00035 int error_threshold = 500;
00036 int is_determinate = 0;    // test only top n moves.  0 for all
00037 int max_depth, non_determinate_depth;
00038 double ratio;              // use moves[n+1] when the weight[n+1] >= ratio*weight[n]
00039 bool is_quick = false;
00040 
00041 boost::shared_ptr<osl::NumEffectState> state_to_compare;
00042 size_t state_count = 0;
00043 
00051 int qsearch(const osl::state::SimpleState &s, 
00052             const osl::Move& lastMove)
00053 {
00054   if (is_quick) return 0;
00055 
00056   typedef osl::search::QuiescenceSearch2<osl::eval::PieceEval> qsearch_t;
00057   osl::state::NumEffectState state(s);
00058   osl::search::SimpleHashTable table(100000, -1, false);
00059   osl::search::SearchState2Core::checkmate_t checkmate_searcher;
00060   osl::search::SearchState2Core core(state, checkmate_searcher);
00061   qsearch_t qs(core, table);
00062   osl::eval::PieceEval ev(state);
00063   return qs.search(state.turn(), ev, lastMove, 4);
00064 }
00065 
00066 void showStatistics(const std::deque<int>& src)
00067 {
00068   double sum, mean, var, dev, skew, kurt;
00069   osl::misc::computeStats(src.begin(), src.end(), sum, mean, var, dev, skew, kurt);
00070 
00071   std::cout << boost::format(" total: %g\n")  % src.size()
00072             << boost::format(" mean:  %g\n") % mean
00073             << boost::format(" dev:   %g\n")  % dev;
00074 }
00075 
00076 void printUsage(std::ostream& out, 
00077                 char **argv,
00078                 const boost::program_options::options_description& command_line_options)
00079 {
00080   out <<
00081     "Usage: " << argv[0] << " [options] <a_joseki_file.dat>\n"
00082       << command_line_options 
00083       << std::endl;
00084 }
00085 
00086 void showInfoOfState(osl::record::opening::WeightedBook& book, const int state_index)
00087 {
00088   osl::record::KanjiPrint printer(std::cerr, 
00089                                   boost::shared_ptr<osl::record::Characters>(
00090                                             new osl::record::KIFCharacters())
00091                                   );
00092 
00093   std::cout << boost::format("state_index: %g\n") % state_index
00094             << boost::format("black win:   %g\n") % book.getBlackWinCount(state_index)
00095             << boost::format("white win:   %g\n") % book.getWhiteWinCount(state_index);
00096 
00097   std::cout << "\nTarget state:\n";
00098   printer.print(book.getBoard(state_index));
00099   std::cout << "\n";
00100 
00101   WMoveContainer moves = book.getMoves(state_index);
00102   std::cout << boost::format("found %g moves\n") % moves.size();
00103   std::sort(moves.begin(), moves.end(), osl::record::opening::WMoveSort());
00104   for (WMoveContainer::const_iterator each = moves.begin();
00105        each != moves.end(); ++each)
00106   {
00107     std::cout << boost::format("[%g] %g") % each->getWeight() % each->getMove();
00108     const int next_index = each->getStateIndex();
00109     std::cout << "\n";
00110     printer.print(book.getBoard(next_index));
00111   }
00112 }
00113 
00114 
00115 void doMain(const std::string& file_name)
00116 {
00117   osl::record::KanjiPrint printer(std::cerr, 
00118                                   boost::shared_ptr<osl::record::Characters>(
00119                                             new osl::record::KIFCharacters())
00120                                   );
00121   if (vm.count("verbose"))
00122     std::cout << boost::format("Opening... %s\n ") % file_name;
00123   osl::record::opening::WeightedBook book(file_name.c_str());
00124 
00125   if (vm.count("verbose"))
00126     std::cout << boost::format("Total states: %d\n") % book.getTotalState();
00127   bool states[book.getTotalState()]; // mark states that have been visited.
00128   memset(states, 0, sizeof(bool) * book.getTotalState());
00129   boost::progress_display progress(book.getTotalState());
00130 
00131   int state_index_to_compare = -1;
00132   if (state_to_compare)
00133     state_index_to_compare = book.getStateIndex(*state_to_compare);
00134 
00135   typedef std::pair<int, int> state_depth_t;
00136   osl::stl::vector<state_depth_t> stateToVisit;
00137 
00138   if (vm.count("verbose"))
00139     std::cout << boost::format("Start index: %d\n)") % book.getStartState();
00140   stateToVisit.push_back(state_depth_t(book.getStartState(), 1)); // root is 1
00141   // depth-1手目からdepth手目のstate。depth手目はまだ指されていない(これか
00142   // らdepth手目)
00143 
00144   typedef std::pair<int, int> eval_depth_t;
00145   std::deque<eval_depth_t> evals;
00146   long finishing_games = 0;
00147 
00148   while (!stateToVisit.empty())
00149   {
00150     const state_depth_t state_depth = stateToVisit.back();
00151     if (vm.count("verbose"))
00152       std::cout << boost::format("Visiting... %d\n") % state_depth.first;
00153     const int stateIndex = state_depth.first;
00154     const int depth      = state_depth.second;
00155     stateToVisit.pop_back();
00156     states[stateIndex] = true;
00157     ++progress;
00158 
00159 
00160     // see if the state presents in the book
00161     if (state_to_compare && 
00162         state_index_to_compare == stateIndex)
00163       ++state_count;
00164 
00165     WMoveContainer moves = book.getMoves(stateIndex);
00166     if (vm.count("verbose"))
00167       std::cout << boost::format("  #moves... %d\n") % moves.size();
00168     
00169     // 自分(the_player)の手番では、良い手のみ指す
00170     // 相手はどんな手を指してくるか分からない
00171     if ( !moves.empty() &&
00172           ((the_player == osl::BLACK && depth % 2 == 1) ||
00173            (the_player == osl::WHITE && depth % 2 == 0)) )
00174     {
00175       std::sort(moves.begin(), moves.end(), osl::record::opening::WMoveSort());
00176       int min = 1;
00177       if (is_determinate) 
00178       {
00179         min = moves.at(0).getWeight();
00180         if (depth <= non_determinate_depth) 
00181         {
00182           for (int i=1; i<=std::min(is_determinate, (int)moves.size()-1); ++i) 
00183           {
00184             const int weight = moves.at(i).getWeight();
00185             if ((double)weight < (double)moves.at(i-1).getWeight()*ratio)
00186               break;
00187             min = weight;
00188           }
00189         }
00190       }
00191       // Do not play 0-weighted moves.
00192       if (min == 0) min = 1;
00193 
00194       WMoveContainer::iterator each = moves.begin();
00195       for (; each != moves.end(); ++each)
00196       {
00197         if (each->getWeight() < min)
00198           break;
00199       }
00200       moves.erase(each, moves.end());
00201     }
00202 
00203     if (moves.empty() || depth > max_depth) // found leaves
00204     {
00205       const osl::state::NumEffectState state(book.getBoard(stateIndex));
00206       const int value = qsearch(state, osl::Move::PASS(alt(state.turn())));
00207 
00208       if ( (the_player == osl::BLACK && value < -1 * error_threshold) ||
00209            (the_player == osl::WHITE && value > error_threshold) )
00210       {
00211         ++finishing_games;
00212         if (is_dump)
00213         {
00214           std::cerr << std::endl;
00215           std::cerr << "eval: " << value << std::endl;
00216           printer.print(state);
00217           std::cerr << "piece value:" << osl::PieceEval(state).value() << "\n" << state;
00218         }
00219       }
00220       else
00221       {
00222         evals.push_back(eval_depth_t(value, depth));
00223       }
00224       continue;
00225     }
00226 
00227     // 結果の再現性を高めるため、visitの順番を決める
00228     std::sort(moves.begin(), moves.end(), osl::record::opening::WMoveMoveSort());
00229 
00230     // recursively search the tree
00231     for (std::vector<osl::record::opening::WMove>::const_iterator each = moves.begin();
00232          each != moves.end(); ++each)
00233     {
00234       // consistancy check
00235       const osl::SimpleState state(book.getBoard(stateIndex));
00236       const osl::hash::HashKey hash(state);
00237       const int nextIndex = each->getStateIndex();
00238       const osl::SimpleState next_state(book.getBoard(nextIndex));
00239       const osl::hash::HashKey next_hash(next_state);
00240       const osl::hash::HashKey moved_hash = hash.newMakeMove(each->getMove());
00241       if (moved_hash != next_hash)
00242         throw std::string("Illegal move found.");
00243 
00244       if (! states[nextIndex])
00245         stateToVisit.push_back(state_depth_t(nextIndex, depth+1));
00246     } // each wmove
00247   } // while loop
00248 
00249   // Show the result
00250   std::cout << std::endl;
00251   std::cout << boost::format("Book: %s\n") % file_name;
00252   std::cout << boost::format("Player: %s\n") % the_player;
00253   std::cout << "FU=128 points\n";
00254   std::cout << 
00255     boost::format("#states: %d (+ %d finishing games over %d points; max %d)\n") 
00256                   % evals.size() 
00257                   % finishing_games 
00258                   % error_threshold 
00259                   % max_depth;
00260   {
00261     std::cout << "Eval\n";
00262     std::deque<int> tmp;
00263     for (std::deque<eval_depth_t>::const_iterator each = evals.begin();
00264          each != evals.end(); ++each)
00265       tmp.push_back(each->first);
00266     showStatistics(tmp);
00267   }
00268   {
00269     std::cout << "Depth\n";
00270     std::deque<int> tmp;
00271     for (std::deque<eval_depth_t>::const_iterator each = evals.begin();
00272          each != evals.end(); ++each)
00273       tmp.push_back(each->second);
00274     showStatistics(tmp);
00275   }
00276   if (state_to_compare)
00277   {
00278     std::cout << "\nthe state hits: " << state_count << std::endl;
00279     printer.print(*state_to_compare);
00280 
00281     const int stateIndex  = book.getStateIndex(*state_to_compare);
00282     const std::vector<int> parents = book.getParents(stateIndex);
00283     if (parents.empty())
00284     {
00285       std::cout << "\nNo parent\n";
00286     }
00287     else
00288     {
00289       int i = 0;
00290       for (std::vector<int>::const_iterator each = parents.begin();
00291            each != parents.end(); ++each, ++i)
00292       {
00293         std::cout << boost::format("\n--- Parent: %g ---\n ") % i;
00294         showInfoOfState(book, *each);
00295       }
00296     }
00297 
00298   }
00299 }
00300 
00301 
00302 int main(int argc, char **argv)
00303 {
00304   std::string player_str;
00305   std::string file_name;
00306   size_t csa_move_index;
00307   std::string csa_file_name;
00308 
00309   bp::options_description command_line_options;
00310   command_line_options.add_options()
00311     ("player,p", bp::value<std::string>(&player_str)->default_value("black"),
00312      "specify a player, black or white, in whose point of view the book is validated. "
00313      "default black.")
00314     ("input-file,f", bp::value<std::string>(&file_name)->default_value("./joseki.dat"),
00315      "a joseki file to validate.")
00316     ("dump", bp::value<bool>(&is_dump)->default_value(false),
00317      "dump finishing games' states")
00318     ("threshold", bp::value<int>(&error_threshold)->default_value(500),
00319      "threshold of evaluatoin value to recognize a finishing game.")
00320     ("determinate", bp::value<int>(&is_determinate)->default_value(0),
00321      "only search the top n moves.  (0 for all,  1 for determinate).")
00322     ("non-determinate-depth", bp::value<int>(&non_determinate_depth)->default_value(100),
00323      "use the best move where the depth is greater than this value")
00324     ("max-depth", bp::value<int>(&max_depth)->default_value(100),
00325      "do not go beyond this depth from the root")
00326     ("ratio", bp::value<double>(&ratio)->default_value(0.0),
00327      "skip move[i] (i >= n), if weight[n] < weight[n-1]*ratio")
00328     ("csa-move", bp::value<size_t>(&csa_move_index)->default_value(1),
00329      "n-th-move state in the csa file")
00330     ("csa", bp::value<std::string>(&csa_file_name)->default_value(""),
00331      "a csa file name. See if a state in the game exists in the book or not.")
00332     ("quick", bp::value<bool>(&is_quick)->default_value(false),
00333      "skip quiescence search.")
00334     ("verbose,v", "output verbose messages.")
00335     ("help,h", "show this help message.");
00336   bp::positional_options_description p;
00337   p.add("input-file", 1);
00338 
00339   try
00340   {
00341     bp::store(
00342       bp::command_line_parser(
00343         argc, argv).options(command_line_options).positional(p).run(), vm);
00344     bp::notify(vm);
00345     if (vm.count("help"))
00346     {
00347       printUsage(std::cout, argv, command_line_options);
00348       return 0;
00349     }
00350   }
00351   catch (std::exception &e)
00352   {
00353     std::cerr << "error in parsing options\n"
00354               << e.what() << std::endl;
00355     printUsage(std::cerr, argv, command_line_options);
00356     return 1;
00357   }
00358 
00359   if (player_str == "black")
00360     the_player = osl::BLACK;
00361   else if (player_str == "white")
00362     the_player = osl::WHITE;
00363   else
00364   {
00365     printUsage(std::cerr, argv, command_line_options);
00366     return 1;
00367   }
00368 
00369   if (!csa_file_name.empty())
00370   {
00371     is_quick = true;
00372     const osl::record::csa::CsaFile csa(csa_file_name);
00373     const osl::record::Record record = csa.getRecord();
00374     const osl::stl::vector<osl::Move> moves = record.getMoves();
00375     const osl::SimpleState initialState = record.getInitialState();
00376     state_to_compare.reset(new osl::NumEffectState(initialState));
00377 
00378     if (csa_move_index < 1) csa_move_index = 1;
00379     if (csa_move_index > moves.size()) csa_move_index = moves.size();
00380     if ( (the_player == osl::BLACK && csa_move_index%2 == 0) ||
00381          (the_player == osl::WHITE && csa_move_index%2 == 1) )
00382     {
00383       std::cout << "Invalid csa move index: " << csa_move_index << std::endl;
00384       return -1;
00385     }
00386     for (size_t i=0; i < csa_move_index; i++)
00387     {
00388        const osl::Move& move = moves[i];
00389        state_to_compare->makeMove(move);
00390     }
00391   }
00392 
00393   doMain(file_name);
00394 
00395   return 0;
00396 }
00397 // ;;; Local Variables:
00398 // ;;; mode:c++
00399 // ;;; c-basic-offset:2
00400 // ;;; End:
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines