diff --git a/go/src/client/main.go b/go/src/client/main.go
index 8aca974ce..f8bccf7df 100644
--- a/go/src/client/main.go
+++ b/go/src/client/main.go
@@ -78,7 +78,7 @@ func getExtraParams() map[string]string {
return map[string]string{
"user": *USER,
"password": *PASSWORD,
- "version": "9",
+ "version": "10",
}
}
diff --git a/lc0/meson.build b/lc0/meson.build
index e6ffd58c2..8ed74d2da 100644
--- a/lc0/meson.build
+++ b/lc0/meson.build
@@ -42,9 +42,12 @@ cuda_gen = generator(nvcc,
)
files = [
+ 'src/analyzer/analyzer.cc',
+ 'src/analyzer/table.cc',
'src/chess/bitboard.cc',
'src/chess/board.cc',
'src/chess/position.cc',
+ 'src/chess/uciloop.cc',
'src/mcts/node.cc',
'src/mcts/search.cc',
'src/neural/cache.cc',
@@ -55,16 +58,16 @@ files = [
'src/neural/network_mux.cc',
'src/neural/network_random.cc',
'src/neural/network_tf.cc',
- 'src/utils/transpose.cc',
'src/utils/commandline.cc',
'src/utils/optionsdict.cc',
+ 'src/utils/optionsparser.cc',
'src/utils/random.cc',
+ 'src/utils/string.cc',
+ 'src/utils/transpose.cc',
'src/engine.cc',
- 'src/optionsparser.cc',
'src/selfplay/game.cc',
'src/selfplay/tournament.cc',
'src/selfplay/loop.cc',
- 'src/uciloop.cc',
cuda_gen.process(cuda_files)
]
diff --git a/lc0/src/analyzer/analyzer.cc b/lc0/src/analyzer/analyzer.cc
new file mode 100644
index 000000000..5fb5f420b
--- /dev/null
+++ b/lc0/src/analyzer/analyzer.cc
@@ -0,0 +1,264 @@
+/*
+ This file is part of Leela Chess Zero.
+ Copyright (C) 2018 The LCZero Authors
+
+ Leela Chess 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.
+
+ Leela Chess 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 Leela Chess. If not, see .
+*/
+
+#include "analyzer/analyzer.h"
+#include "analyzer/table.h"
+#include "mcts/search.h"
+#include "neural/factory.h"
+#include "neural/loader.h"
+#include "utils/optionsparser.h"
+#include "utils/string.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace lczero {
+namespace {
+const char* kTsvReportStr = "Filename of the tab-separated report file";
+const char* kTxtReportStr = "Filename of the text report file";
+const char* kMovesStr = "Moves in UCI format, space separated";
+const char* kMovesToAnalyzeStr = "Number of (last) moves to analyze";
+const char* kNodesStr = "(comma separated) How many nodes to calculate";
+const char* kTrainExamplesStr =
+ "How many examples of training data to generate";
+const char* kWeightsStr = "Network weights file path";
+const char* kNnBackendStr = "NN backend to use";
+const char* kNnBackendOptionsStr = "NN backend parameters";
+
+const char* kAutoDiscover = "";
+} // namespace
+
+Analyzer::Analyzer() {
+ options_parser_.AddContext("play");
+ options_parser_.AddContext("training");
+ play_options_ = &options_parser_.GetOptionsDict("play");
+ play_options_ = &options_parser_.GetOptionsDict("training");
+
+ options_parser_.Add(kTsvReportStr, "tsv-report");
+ options_parser_.Add(kTxtReportStr, "txt-report");
+ options_parser_.Add(kMovesStr, "moves");
+ options_parser_.Add(kMovesToAnalyzeStr, 1, 999, "num-moves") = 4;
+ options_parser_.Add(kNodesStr, "nodes-list") =
+ "10,50,100,200,400,600,800,1200,1600,5000,10000";
+ options_parser_.Add(kTrainExamplesStr, 1, 999,
+ "training-examples") = 10;
+ options_parser_.Add(kWeightsStr, "weights", 'w') =
+ kAutoDiscover;
+
+ // TODO(mooskagh) Move all that to network factory.
+ const auto backends = NetworkFactory::Get()->GetBackendsList();
+ options_parser_.Add(kNnBackendStr, backends, "backend") =
+ backends.empty() ? "" : backends[0];
+ options_parser_.Add(kNnBackendOptionsStr, "backend-opts");
+
+ Search::PopulateUciParams(&options_parser_);
+
+ // Overriding default options of search.
+ auto defaults = options_parser_.GetMutableDefaultsOptions();
+ defaults->Set(Search::kMiniBatchSizeStr, 1); // Minibatch = 1
+ defaults->Set(Search::kSmartPruningStr, false); // No smart pruning
+ defaults->Set(Search::kVerboseStatsStr, true); // Verbose stats
+}
+
+void Analyzer::GatherStats(Table3d* table, const Node* root_node,
+ std::string& col, bool flip) {
+ uint64_t total_n = 0;
+ float factor = play_options_->Get(Search::kCpuctStr) *
+ std::sqrt(std::max(root_node->GetN(), 1u));
+ const float parent_q =
+ -root_node->GetQ(0) - play_options_->Get(Search::kFpuReductionStr);
+
+ for (Node* node : root_node->Children()) {
+ const auto n = node->GetNStarted();
+ total_n += n;
+ const auto u = factor * node->GetU();
+ const auto q = node->GetQ(parent_q);
+ const auto move = node->GetMove(flip).as_string();
+ table->Add3dVal(col, move, "N", std::to_string(n));
+ table->Add3dVal(col, move, "U", std::to_string(u));
+ table->Add3dVal(col, move, "Q", std::to_string(q));
+ table->Add3dVal(col, move, "U+Q", std::to_string(u + q));
+ }
+
+ for (Node* node : root_node->Children()) {
+ auto n = node->GetNStarted();
+ table->Add3dVal(col, node->GetMove(flip).as_string(), "N%",
+ std::to_string(static_cast(n) / total_n));
+ }
+}
+
+void Analyzer::RunOnePosition(const std::vector& moves) {
+ NNCache cache(200000);
+ NodeTree tree;
+ tree.ResetToPosition(ChessBoard::kStartingFen, moves);
+
+ auto nodeses = ParseIntList(play_options_->Get(kNodesStr));
+ std::sort(nodeses.begin(), nodeses.end());
+
+ Table3d table;
+ std::vector cols;
+
+ // Run search in increasing number of nodes.
+ for (int nodes : nodeses) {
+ WriteToLog("Nodes: " + std::to_string(nodes));
+ SearchLimits limits;
+ limits.visits = nodes;
+
+ Search search(tree, network_.get(),
+ std::bind(&Analyzer::OnBestMove, this, std::placeholders::_1),
+ std::bind(&Analyzer::OnInfo, this, std::placeholders::_1),
+ limits, *play_options_, &cache);
+
+ search.RunBlocking(1);
+
+ auto col = std::to_string(nodes);
+ cols.push_back(col);
+ GatherStats(&table, tree.GetCurrentHead(), col, tree.IsBlackToMove());
+ table.AddColVal(col, "bestmove", search.GetBestMove().first.as_string());
+ }
+
+ // Fetch MCTS-agnostic per-move stats P and V.
+ std::vector nodes;
+ for (Node* iter : tree.GetCurrentHead()->Children()) {
+ nodes.emplace_back(iter);
+ }
+ std::sort(nodes.begin(), nodes.end(), [](const Node* a, const Node* b) {
+ return a->GetNStarted() > b->GetNStarted();
+ });
+ std::vector rows;
+ for (const Node* node : nodes) {
+ auto move = node->GetMove(tree.IsBlackToMove()).as_string();
+ rows.push_back(move);
+ table.AddRowVal(move, "P", std::to_string(node->GetP()));
+ table.AddRowVal(move, "V", std::to_string(node->GetV()));
+ }
+
+ // Dump table to log.
+ auto lines = table.RenderTable(cols, rows, {"N", "N%", "U", "Q", "U+Q"},
+ {"P", "V"}, {"bestmove"});
+ for (const auto& line : lines) WriteToTsvLog(line);
+}
+
+void Analyzer::Run() {
+ if (!options_parser_.ProcessAllFlags()) return;
+
+ // Open log files.
+ if (!play_options_->Get(kTxtReportStr).empty()) {
+ log_.open(play_options_->Get(kTxtReportStr).c_str());
+ }
+ if (!play_options_->Get(kTsvReportStr).empty()) {
+ tsvlog_.open(play_options_->Get(kTsvReportStr).c_str());
+ }
+
+ // Load network
+ InitializeNetwork();
+
+ // Parse moves.
+ std::vector moves_str =
+ StrSplitAtWhitespace(play_options_->Get(kMovesStr));
+ std::vector moves;
+ for (const auto& move : moves_str) moves.emplace_back(move);
+
+ for (int i = 0; i < play_options_->Get(kMovesToAnalyzeStr); ++i) {
+ // Write position to logs.
+ std::string initial_pos;
+ if (moves_str.empty()) {
+ WriteToLog("Position: startpos");
+ WriteToTsvLog({"Startpos."});
+ } else {
+ WriteToLog("Position: moves " + StrJoin(moves_str));
+ WriteToTsvLog({"Moves " + StrJoin(moves_str)});
+ }
+
+ // Run Mcts at different depths.
+ RunOnePosition(moves);
+
+ // Run training several times.
+
+ if (moves.empty()) break;
+ moves.pop_back();
+ moves_str.pop_back();
+ WriteToTsvLog({});
+ }
+
+ // DumpFlags();
+}
+
+void Analyzer::InitializeNetwork() {
+ std::string network_path = play_options_->Get(kWeightsStr);
+ std::string backend = play_options_->Get(kNnBackendStr);
+ std::string backend_options =
+ play_options_->Get(kNnBackendOptionsStr);
+
+ std::string net_path = network_path;
+ if (net_path == kAutoDiscover) {
+ net_path = DiscoveryWeightsFile();
+ }
+ Weights weights = LoadWeightsFromFile(net_path);
+
+ OptionsDict network_options = OptionsDict::FromString(
+ backend_options, &options_parser_.GetOptionsDict());
+
+ network_ = NetworkFactory::Get()->Create(backend, weights, network_options);
+}
+
+void Analyzer::WriteToLog(const std::string& line) const {
+ std::cout << line << std::endl;
+ if (log_) log_ << line << std::endl;
+}
+
+void Analyzer::WriteToTsvLog(const std::vector& line) const {
+ if (!tsvlog_) return;
+ bool first = true;
+ for (const auto& chunk : line) {
+ if (first) {
+ first = false;
+ } else {
+ tsvlog_ << "\t";
+ }
+ tsvlog_ << chunk;
+ }
+ tsvlog_ << std::endl;
+}
+
+void Analyzer::OnBestMove(const BestMoveInfo& move) const {
+ WriteToLog("BestMove: " + move.bestmove.as_string());
+}
+
+void Analyzer::OnInfo(const ThinkingInfo& info) const {
+ std::string res = "Info";
+ if (info.depth >= 0) res += " depth " + std::to_string(info.depth);
+ if (info.seldepth >= 0) res += " seldepth " + std::to_string(info.seldepth);
+ if (info.time >= 0) res += " time " + std::to_string(info.time);
+ if (info.nodes >= 0) res += " nodes " + std::to_string(info.nodes);
+ if (info.score) res += " score cp " + std::to_string(*info.score);
+ if (info.nps >= 0) res += " nps " + std::to_string(info.nps);
+
+ if (!info.pv.empty()) {
+ res += " pv";
+ for (const auto& move : info.pv) res += " " + move.as_string();
+ }
+ if (!info.comment.empty()) res += " string " + info.comment;
+ WriteToLog(res);
+}
+
+} // namespace lczero
\ No newline at end of file
diff --git a/lc0/src/analyzer/analyzer.h b/lc0/src/analyzer/analyzer.h
new file mode 100644
index 000000000..64c004622
--- /dev/null
+++ b/lc0/src/analyzer/analyzer.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of Leela Chess Zero.
+ Copyright (C) 2018 The LCZero Authors
+
+ Leela Chess 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.
+
+ Leela Chess 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 Leela Chess. If not, see .
+*/
+
+#pragma once
+
+#include
+#include "analyzer/table.h"
+#include "chess/board.h"
+#include "chess/callbacks.h"
+#include "mcts/node.h"
+#include "neural/network.h"
+#include "utils/optionsparser.h"
+
+namespace lczero {
+
+class Analyzer {
+ public:
+ Analyzer();
+ void Run();
+
+ private:
+ void RunOnePosition(const std::vector& position);
+
+ void WriteToLog(const std::string& line) const;
+ void WriteToTsvLog(const std::vector& line) const;
+
+ void InitializeNetwork();
+ void OnBestMove(const BestMoveInfo& move) const;
+ void OnInfo(const ThinkingInfo& info) const;
+ void GatherStats(Table3d* table, const Node* root_node, std::string& col,
+ bool flip);
+
+ std::unique_ptr network_;
+ OptionsParser options_parser_;
+ const OptionsDict* play_options_;
+ // const OptionsDict* training_options_;
+ mutable std::ofstream log_;
+ mutable std::ofstream tsvlog_;
+};
+
+} // namespace lczero
\ No newline at end of file
diff --git a/lc0/src/analyzer/table.cc b/lc0/src/analyzer/table.cc
new file mode 100644
index 000000000..7e7eff4ea
--- /dev/null
+++ b/lc0/src/analyzer/table.cc
@@ -0,0 +1,109 @@
+/*
+ This file is part of Leela Chess Zero.
+ Copyright (C) 2018 The LCZero Authors
+
+ Leela Chess 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.
+
+ Leela Chess 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 Leela Chess. If not, see .
+*/
+
+#include "analyzer/table.h"
+#include
+
+namespace lczero {
+
+void Table3d::Add3dVal(const std::string& col, const std::string& row,
+ const std::string& metric, const std::string& val) {
+ data_3d_[col][row][metric] = val;
+}
+
+void Table3d::AddRowVal(const std::string& row, const std::string& metric,
+ const std::string& val) {
+ data_row_[row][metric] = val;
+}
+
+void Table3d::AddColVal(const std::string& col, const std::string& metric,
+ const std::string& val) {
+ data_col_[col][metric] = val;
+}
+
+namespace {
+std::string BuildDimensionsStr(int cols, int rows, int attrs, int rattrs,
+ int cattrs) {
+ std::ostringstream oss;
+ oss << "(##" << cols << 'x' << rows << 'x' << attrs << 'x' << rattrs << 'x'
+ << cattrs << ")";
+ return oss.str();
+}
+
+} // namespace
+
+std::vector> Table3d::RenderTable(
+ const std::vector& cols, const std::vector& rows,
+ const std::vector& attrs,
+ const std::vector& row_attrs,
+ const std::vector& col_attrs) {
+ std::vector> lines;
+ const bool has_row_attrs = !row_attrs.empty();
+
+ std::vector line;
+
+ // Header.
+ // Topleft cell contains format for appscript to know the table dimensions.
+ line.push_back(BuildDimensionsStr(cols.size(), rows.size(), attrs.size(),
+ row_attrs.size(), col_attrs.size()));
+ for (int i = 0; i < (has_row_attrs ? 3 : 1); ++i) line.emplace_back();
+ for (const auto& col : cols) line.emplace_back(col);
+ lines.emplace_back(std::move(line));
+
+ // Col-level values.
+ for (const auto& attr : col_attrs) {
+ line.clear();
+ for (int i = 0; i < (has_row_attrs ? 3 : 1); ++i) line.emplace_back();
+ line.push_back(attr + ":");
+ for (const auto& col : cols) line.emplace_back(data_col_[col][attr]);
+ lines.emplace_back(std::move(line));
+ }
+
+ // Main table.
+ for (const auto& row : rows) {
+ const int stripes = std::max(attrs.size(), row_attrs.size());
+ for (int i = 0; i < stripes; ++i) {
+ line.clear();
+ if (i == 0) {
+ line.push_back(row);
+ } else {
+ line.emplace_back();
+ }
+ if (has_row_attrs) {
+ if (i < row_attrs.size()) {
+ line.push_back(row_attrs[i] + ":");
+ line.push_back(data_row_[row][row_attrs[i]]);
+ } else {
+ line.emplace_back();
+ line.emplace_back();
+ }
+ }
+ if (i < attrs.size()) {
+ auto attr = attrs[i];
+ line.emplace_back(attr + ":");
+ for (const auto& col : cols)
+ line.emplace_back(data_3d_[col][row][attr]);
+ }
+ lines.emplace_back(std::move(line));
+ }
+ }
+
+ return lines;
+}
+
+} // namespace lczero
\ No newline at end of file
diff --git a/lc0/src/analyzer/table.h b/lc0/src/analyzer/table.h
new file mode 100644
index 000000000..eb40a1183
--- /dev/null
+++ b/lc0/src/analyzer/table.h
@@ -0,0 +1,55 @@
+/*
+ This file is part of Leela Chess Zero.
+ Copyright (C) 2018 The LCZero Authors
+
+ Leela Chess 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.
+
+ Leela Chess 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 Leela Chess. If not, see .
+*/
+
+#pragma once
+
+#include