Skip to content

Commit

Permalink
- ふかうら王がdf-pnで見つけている詰みを見逃すことがある件 対応その1
Browse files Browse the repository at this point in the history
  - #286

⇨ GenerateAllLegalMoves、ふかうら王がきちんと反映させていなかった。修正。
  • Loading branch information
yaneurao committed Nov 29, 2024
1 parent f30c136 commit 492b4c6
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 4 deletions.
150 changes: 149 additions & 1 deletion source/engine/dlshogi-engine/PvMateSearch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ namespace dlshogi
//extern std::unique_ptr<NodeTree> tree;
//extern const Position* pos_root;

PvMateSearcher::PvMateSearcher(const int nodes, DlshogiSearcher* dl_searcher) :
PvMateSearcher::PvMateSearcher(const int nodes, DlshogiSearcher* dl_searcher /*, int thread_id*/) :
ready_th(true),
term_th(false),
th(nullptr),
dfpn(Mate::Dfpn::DfpnSolverType::Node48bitOrdering),
dl_searcher(dl_searcher)
/*,thread_id(thread_id)*/
{
// 子ノードを展開するから、探索ノード数の8倍ぐらいのメモリを要する
dfpn.alloc_by_nodes_limit((size_t)(nodes * 8));
Expand Down Expand Up @@ -174,6 +175,140 @@ namespace dlshogi
}
}

#if 0
// BFS型の詰み探索をする。depth = (df-pnを呼び出す)残り探索depth。
// stopがくるか、df-pnの探索をしたならtrueを返す。
void PvMateSearcher::SearchInnerBFS(Position& pos, Node* uct_node, ChildNode* child_node, bool root, int depth)
{
// 停止
if (stop) return;

// いまから詰み探索済みフラグをチェックするのでlockが必要
Node::mtx_dfpn.lock();

// このnodeは詰み探索済みであるか?
if (!(uct_node->dfpn_proven_unsolvable || uct_node->dfpn_checked))
{
// 詰み探索まだ。

// いったん詰み探索をしたことにする。(他のスレッドがこの局面を重複して探索しないように。)
uct_node->dfpn_checked = true;
Node::mtx_dfpn.unlock();

// 詰みの場合、ノードを更新
Move mate_move = dfpn.mate_dfpn(pos, nodes_limit);
if (is_ok(mate_move)) {
// 詰みを発見した。

// rootで詰みを発見したのでメッセージを出力しておく。
if (root)
{
// 何手詰めか
int mate_ply = dfpn.get_mate_ply();
sync_cout << "info string found the root mate by df-pn , move = " << to_usi_string(mate_move) << " ,ply = " << mate_ply << sync_endl;

// 手数保存しておく。
//uct_node->dfpn_mate_ply = mate_ply;

// 読み筋を出力する。
std::stringstream pv;
for (auto move : dfpn.get_pv())
pv << ' ' << to_usi_string(move);
sync_cout << "info score mate " << mate_ply << " pv" << pv.str() << sync_endl;

// moveの指し手をSetLose()する。
// rootのchildは存在することが保証されている。(Expandしてから探索を開始するので)
auto* child = uct_node->child.get();
for (size_t i = 0; i < uct_node->child_num; ++i)
if (child[i].getMove() == mate_move)
// Node::Moveは上位8bitを使っているので.moveではなく.getMove()を用いる。
{
child[i].SetLose();
break;
}

}
else {

// SetWinしておけばPV line上なので次のUctSearchで更新されるはず。
// ここがrootなら、これでrootは詰みを発見するので自動的に探索が停止する。
// ゆえに、rootであるかの判定等は不要である。
child_node->SetWin();
// ⇨ moveの指し手を指したら、子ノードの局面に即詰みがあるということなので
// 現局面は負けの局面であることに注意。

}
}
else if (stop) {
// 途中で停止された場合、未探索に戻す。
std::lock_guard<std::mutex> lock(Node::mtx_dfpn);
uct_node->dfpn_checked = false;
}
// 探索中にPVが変わっている可能性があるため、ルートに戻る。
return;
}
else {
Node::mtx_dfpn.unlock();

/*

⇑⇑⇑
ここまでは、BFSじゃないほうと同一のロジック

ここからが違う。上位N個に対して展開していく。
⇓⇓⇓

*/

// 残り深さがあるなら..
if (depth < 1)
return;

// 詰み探索済みであることがわかったので、
// 子が展開済みの場合、PV上の次の手へ

// 未展開の場合、終了する
if (!uct_node->IsEvaled() || !uct_node->child) {
return;
}

// まだ子Nodeが生成されていないか?
if (!uct_node->child_nodes)
return;

// 訪問回数が最大の子ノードを選択
// ⇨ rootの時だけ時々second以降に行ってもいいかも..?
int indices[MAX_MOVES];
int N = 4; // 上位4個ずつ。
select_nth_child_node(uct_node, N, indices);

for (int i = 0; i < N; ++i)
{
int next_index = indices[i];
// 上位N個がないから-1がpaddingされてるパターン
if (next_index == -1)
break;

auto uct_next = uct_node->child_nodes[next_index].get();
// 次のnodeがまだ展開されてないパターン
if (!uct_next)
continue;

// 1手進める。
StateInfo st;
Move m = uct_node->child[next_index].getMove();
sync_cout << to_usi_string(m) << sync_endl;
pos.do_move(m, st);

// 再帰的に子を辿っていく。
SearchInnerBFS(pos, uct_next, &uct_node->child[next_index], false, depth - 1);

pos.undo_move(m);
}
}
}
#endif

void PvMateSearcher::Run()
{
// th == nullptrなら、poolしているthread自体がないのでとりあえず生成をする。
Expand All @@ -192,7 +327,20 @@ namespace dlshogi

// PV上の詰み探索
auto* tree = dl_searcher->get_node_tree();

SearchInner(pos_copy, tree->GetCurrentHead(), nullptr, true);
#if 0
// 偶数番のスレッドはPV line担当。
// 奇数番のスレッドはBFSしていく担当。
if ((thread_id & 1) == 0)
SearchInner(pos_copy, tree->GetCurrentHead(), nullptr, true);
else
{
// 6手の範囲内を完全に調べ尽くす。
SearchInnerBFS(pos_copy, tree->GetCurrentHead(), nullptr, true, 6);
std::this_thread::yield();
}
#endif
}

std::unique_lock<std::mutex> lk(mtx_th);
Expand Down
6 changes: 6 additions & 0 deletions source/engine/dlshogi-engine/PvMateSearch.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ namespace dlshogi
// 詰み探索を行う局面がないか、詰探索を1回したならば、returnする。
void SearchInner(Position& pos, Node* uct_node, ChildNode* child_node, bool root);

// BFS型の詰み探索をする。depth = (df-pnを呼び出す)残り探索depth。dfpnを呼び出した回数を返す。
//void SearchInnerBFS(Position& pos, Node* uct_node, ChildNode* child_node, bool root, int depth);

//// 探索済みノードを表現する
//static std::set<ChildNode*> searched;
//// ⇑にアクセスするときのmutex
Expand All @@ -71,6 +74,9 @@ namespace dlshogi
// globalにアクセスすると行儀悪いので..
DlshogiSearcher* dl_searcher;

// thread id。この番号によって挙動を変えたい時に用いる。
// int thread_id;

// ===============
// thread pool関連
// ===============
Expand Down
23 changes: 23 additions & 0 deletions source/engine/dlshogi-engine/UctSearch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,29 @@ namespace dlshogi
return select_index;
}

// 訪問回数が上からN個の子ノードを返す。
// N個ない時は、残りが-1で埋まる。
void select_nth_child_node(const Node* uct_node, int n, int(&indices)[MAX_MOVES])
{
const ChildNode* uct_child = uct_node->child.get();
const int child_num = uct_node->child_num;

int index = 0;
for (int i = 0; i < child_num; i++) {
// 勝ち負けが確定していない子に対して。
// あと、訪問回数が10回以上であること。
if (!(uct_child[i].IsWin() || uct_child[i].IsLose() || uct_child[i].move_count < 10))
indices[index++] = i;
}
std::partial_sort(indices, indices + std::min(n, index) , indices + index, [&uct_child](int a, int b) {
return uct_child[a].move_count > uct_child[b].move_count;
});

// 残りをn個まで-1でpaddingする。
for (int i = index; i < n; ++i)
indices[i] = -1;
}

}


Expand Down
4 changes: 4 additions & 0 deletions source/engine/dlshogi-engine/UctSearch.h
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ namespace dlshogi
// 訪問回数が最大の子ノードを選択
unsigned int select_max_child_node(const Node* uct_node);

// 訪問回数が上からN個の子ノードを返す。
// N個ない時は、残りが-1で埋まる。
void select_nth_child_node(const Node* uct_node, int n , int (&indices)[MAX_MOVES]);

}

#endif
Expand Down
4 changes: 1 addition & 3 deletions source/engine/dlshogi-engine/YaneuraOu_dlshogi_bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ void USI::extra_option(USI::OptionsMap& o)
// → Playout数固定。これはNodesLimitでできるので不要。

// PV lineの即詰みを調べるスレッドの数と1局面当たりの最大探索ノード数。
o["PV_Mate_Search_Threads"] << USI::Option(0, 0, 256);
o["PV_Mate_Search_Threads"] << USI::Option(1, 0, 256);
o["PV_Mate_Search_Nodes"] << USI::Option(500000, 0, UINT32_MAX);

// → leaf nodeではdf-pnに変更。
Expand Down Expand Up @@ -158,8 +158,6 @@ void Search::clear()

searcher.SetPvInterval((TimePoint)Options["PV_Interval"]);

searcher.SetGetnerateAllLegalMoves(Options["GenerateAllLegalMoves"]);

// ノードを再利用するかの設定。
searcher.SetReuseSubtree(Options["ReuseSubtree"]);

Expand Down
4 changes: 4 additions & 0 deletions source/engine/dlshogi-engine/dlshogi_searcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,10 @@ namespace dlshogi
// 持ち時間制御は、やねうら王の time managerをそのまま用いる。
search_limits.time_manager.init(limits,pos->side_to_move(),pos->game_ply());

// GenerateAllLegalMovesオプションは、usi.cpp側で、"go"コマンド時に
// Limits.generate_all_legal_movesに反映される。
// ゆえに、エンジン側では、これを反映させなければならない。
SetGetnerateAllLegalMoves(limits.generate_all_legal_moves);
}

// ノード数固定にしたい時は、USIの"go nodes XXX"ででき、これは、SetLimits()で反映するので↓は不要。
Expand Down

0 comments on commit 492b4c6

Please sign in to comment.