00001 //---------------------------------------------------------------------------- 00002 /** @file DfsSolver.cpp 00003 00004 @todo Finish converting over to using HexStates. 00005 */ 00006 //---------------------------------------------------------------------------- 00007 00008 #include "SgSystem.h" 00009 00010 #include "Hex.hpp" 00011 #include "VCSet.hpp" 00012 #include "HexProp.hpp" 00013 #include "HexBoard.hpp" 00014 #include "GraphUtils.hpp" 00015 #include "Resistance.hpp" 00016 #include "DfsSolver.hpp" 00017 #include "Time.hpp" 00018 #include "VCUtils.hpp" 00019 #include "BoardUtils.hpp" 00020 #include "BitsetIterator.hpp" 00021 #include "EndgameUtils.hpp" 00022 #include "ProofUtil.hpp" 00023 00024 #include <cmath> 00025 #include <algorithm> 00026 #include <boost/scoped_ptr.hpp> 00027 00028 using namespace benzene; 00029 00030 //---------------------------------------------------------------------------- 00031 00032 /** Current version of dfs database. 00033 Update this if DfsData is changes to prevent old out-of-data 00034 databases from being loaded. */ 00035 const std::string DfsDB::DFS_DB_VERSION("BENZENE_DFS_DB_VER_0001"); 00036 00037 //---------------------------------------------------------------------------- 00038 00039 DfsSolver::DfsSolver() 00040 : m_positions(0), 00041 m_use_decompositions(true), 00042 m_update_depth(4), 00043 m_shrink_proofs(true), 00044 m_backup_ice_info(true), 00045 m_use_guifx(false), 00046 m_move_ordering(DfsMoveOrderFlags::WITH_MUSTPLAY 00047 | DfsMoveOrderFlags::WITH_RESIST 00048 | DfsMoveOrderFlags::FROM_CENTER) 00049 { 00050 } 00051 00052 DfsSolver::~DfsSolver() 00053 { 00054 } 00055 00056 //---------------------------------------------------------------------------- 00057 00058 HexColor DfsSolver::Solve(const HexState& state, HexBoard& brd, 00059 DfsSolutionSet& solution, DfsStates& positions, 00060 int depthLimit, double timeLimit) 00061 { 00062 m_positions = &positions; 00063 m_depthLimit = depthLimit; 00064 m_timeLimit = timeLimit; 00065 00066 m_aborted = false; 00067 m_start_time = Time::Get(); 00068 m_histogram = DfsHistogram(); 00069 m_last_histogram_dump = 0; 00070 m_statistics = GlobalStatistics(); 00071 m_state.reset(new HexState(state)); 00072 m_workBrd = &brd; 00073 00074 // DfsSolver currently cannot handle permanently inferior cells. 00075 if (m_workBrd->ICE().FindPermanentlyInferior()) 00076 throw BenzeneException("Permanently Inferior not supported " 00077 "in DfsSolver!"); 00078 // Check if state is already solved 00079 DfsData data; 00080 bool win = false; 00081 HexColor toPlay = state.ToPlay(); 00082 if (CheckTransposition(data)) 00083 { 00084 LogInfo() << "DfsSolver: Found cached result!\n"; 00085 win = data.m_win; 00086 solution.m_numMoves = data.m_numMoves; 00087 solution.pv.clear(); 00088 SolverDBUtil::GetVariation(*m_state, positions, solution.pv); 00089 solution.proof = ProofUtil::MaximumProofSet(brd, data.m_win ? 00090 toPlay : !toPlay); 00091 } 00092 else 00093 { 00094 m_workBrd->ComputeAll(toPlay); 00095 m_completed.resize(BITSETSIZE); 00096 PointSequence variation; 00097 win = SolveState(variation, solution); 00098 } 00099 solution.proof &= m_state->Position().GetEmpty(); 00100 m_end_time = Time::Get(); 00101 if (m_aborted) 00102 return EMPTY; 00103 return win ? toPlay : !toPlay; 00104 } 00105 00106 //---------------------------------------------------------------------------- 00107 00108 bool DfsSolver::CheckTransposition(DfsData& data) const 00109 { 00110 return m_positions->Get(*m_state, data); 00111 } 00112 00113 void DfsSolver::StoreState(const DfsData& data, const bitset_t& proof) 00114 { 00115 m_positions->Put(*m_state, data); 00116 const SolverDBParameters& param = m_positions->Parameters(); 00117 if (m_state->Position().NumStones() <= param.m_transStones) 00118 { 00119 HexColor toPlay = m_state->ToPlay(); 00120 if (param.m_useProofTranspositions) 00121 ProofUtil::StoreTranspositions(*m_positions, data, 00122 *m_state, proof, 00123 data.m_win ? toPlay : !toPlay); 00124 if (param.m_useFlippedStates) 00125 ProofUtil::StoreFlippedStates(*m_positions, data, 00126 *m_state, proof, 00127 data.m_win ? toPlay : !toPlay); 00128 } 00129 } 00130 00131 //---------------------------------------------------------------------------- 00132 00133 /** Checks timelimit and SgUserAbort(). Sets m_aborted if necessary, 00134 aborting the search. Returns true if search should be aborted, 00135 false otherwise. */ 00136 bool DfsSolver::CheckAbort() 00137 { 00138 if (!m_aborted) 00139 { 00140 if (SgUserAbort()) 00141 { 00142 m_aborted = true; 00143 LogInfo() << "DfsSolver::CheckAbort(): Abort flag!\n"; 00144 } 00145 else if ((m_timeLimit > 0) && 00146 ((Time::Get() - m_start_time) > m_timeLimit)) 00147 { 00148 m_aborted = true; 00149 LogInfo() << "DfsSolver::CheckAbort(): Timelimit!\n"; 00150 } 00151 } 00152 return m_aborted; 00153 } 00154 00155 /** Returns true if node is terminal. Fills in data if terminal. 00156 Data's bestmove field is not specified here. 00157 */ 00158 bool DfsSolver::HandleTerminalNode(DfsData& data, bitset_t& proof) const 00159 { 00160 int numstones = m_state->Position().NumStones(); 00161 if (EndgameUtils::IsWonGame(*m_workBrd, m_state->ToPlay(), proof)) 00162 { 00163 data.m_win = true; 00164 data.m_numMoves = 0; 00165 data.m_numStates = 1; 00166 m_histogram.terminal[numstones]++; 00167 return true; 00168 } 00169 else if (EndgameUtils::IsLostGame(*m_workBrd, m_state->ToPlay(), proof)) 00170 { 00171 data.m_win = false; 00172 data.m_numMoves = 0; 00173 data.m_numStates = 1; 00174 m_histogram.terminal[numstones]++; 00175 return true; 00176 } 00177 return false; 00178 } 00179 00180 /** Returns true if current data is a terminal node (win/loss), or a 00181 DB/TT hit. If so, info is stored in data. */ 00182 bool DfsSolver::HandleLeafNode(DfsData& data, bitset_t& proof) const 00183 { 00184 if (HandleTerminalNode(data, proof)) 00185 return true; 00186 if (CheckTransposition(data)) 00187 { 00188 HexColor color = m_state->ToPlay(); 00189 proof = ProofUtil::MaximumProofSet(*m_workBrd, 00190 data.m_win ? color : !color); 00191 } 00192 return false; 00193 } 00194 00195 //---------------------------------------------------------------------------- 00196 00197 /** Solves the current state. Handles decompositions if option is 00198 turned on. */ 00199 bool DfsSolver::SolveState(PointSequence& variation, DfsSolutionSet& solution) 00200 { 00201 if (CheckAbort()) 00202 return false; 00203 00204 // Check for VC/DB/TT states 00205 { 00206 DfsData data; 00207 bitset_t proof; 00208 if (HandleLeafNode(data, proof)) 00209 { 00210 solution.pv.clear(); 00211 solution.m_numMoves = data.m_numMoves; 00212 solution.proof = proof; 00213 solution.stats.explored_states = 1; 00214 solution.stats.minimal_explored = 1; 00215 solution.stats.total_states += data.m_numStates; 00216 return data.m_win; 00217 } 00218 } 00219 00220 // Solve decompositions if they exist, otherwise solve the data 00221 // normally. 00222 bool winning_state = false; 00223 { 00224 HexColor color = m_state->ToPlay(); 00225 HexPoint group; 00226 if (m_use_decompositions 00227 && BoardUtils::FindSplittingDecomposition(*m_workBrd, !color, group)) 00228 { 00229 winning_state = SolveDecomposition(variation, solution, group); 00230 } 00231 else 00232 { 00233 winning_state = SolveInteriorState(variation, solution); 00234 } 00235 } 00236 00237 // Shrink, verify, and store proof in DB/TT. 00238 HandleProof(variation, winning_state, solution); 00239 00240 // Dump histogram every 1M moves 00241 if ((m_statistics.played / 1000000) > (m_last_histogram_dump)) 00242 { 00243 LogInfo() << m_histogram.Write() << '\n'; 00244 m_last_histogram_dump = m_statistics.played / 1000000; 00245 } 00246 return winning_state; 00247 } 00248 00249 /** Solves each side of the decompsosition; combines proofs if 00250 necessary. */ 00251 bool DfsSolver::SolveDecomposition(PointSequence& variation, 00252 DfsSolutionSet& solution, HexPoint group) 00253 { 00254 HexColor color = m_state->ToPlay(); 00255 solution.stats.decompositions++; 00256 00257 // Compute the carriers for each side 00258 PointToBitset nbs; 00259 GraphUtils::ComputeDigraph(m_workBrd->GetGroups(), !color, nbs); 00260 bitset_t stopset = nbs[group]; 00261 00262 bitset_t carrier[2]; 00263 carrier[0] = 00264 GraphUtils::BFS(HexPointUtil::colorEdge1(!color), nbs, stopset); 00265 carrier[1] = 00266 GraphUtils::BFS(HexPointUtil::colorEdge2(!color), nbs, stopset); 00267 00268 if ((carrier[0] & carrier[1]).any()) 00269 throw BenzeneException() 00270 << "DfsSolver::SolveDecomposition:\n" 00271 << "Side0:" << m_workBrd->Write(carrier[0]) << '\n' 00272 << "Side1:" << m_workBrd->Write(carrier[1]) << '\n'; 00273 00274 DfsData data; 00275 DfsSolutionSet dsolution[2]; 00276 for (int s = 0; s < 2; ++s) 00277 { 00278 bool win = false; 00279 m_workBrd->PlayStones(!color, 00280 carrier[s^1] & m_workBrd->Const().GetCells(), 00281 color); 00282 00283 bitset_t proof; 00284 if (HandleTerminalNode(data, proof)) 00285 { 00286 win = data.m_win; 00287 dsolution[s].proof = proof; 00288 dsolution[s].m_numMoves = data.m_numMoves; 00289 dsolution[s].pv.clear(); 00290 dsolution[s].stats.expanded_states = 0; 00291 dsolution[s].stats.explored_states = 1; 00292 dsolution[s].stats.minimal_explored = 1; 00293 dsolution[s].stats.total_states = 1; 00294 } 00295 else 00296 win = SolveInteriorState(variation, dsolution[s]); 00297 00298 m_workBrd->UndoMove(); 00299 00300 if (win) 00301 { 00302 solution.pv = dsolution[s].pv; 00303 solution.proof = dsolution[s].proof; 00304 solution.m_numMoves = dsolution[s].m_numMoves; 00305 solution.stats += dsolution[s].stats; 00306 solution.stats.decompositions_won++; 00307 return true; 00308 } 00309 } 00310 00311 // Combine the two losing proofs 00312 solution.pv = dsolution[0].pv; 00313 solution.m_numMoves = dsolution[0].m_numMoves + dsolution[1].m_numMoves; 00314 solution.pv.insert(solution.pv.end(), dsolution[1].pv.begin(), 00315 dsolution[1].pv.end()); 00316 00317 solution.proof = (dsolution[0].proof & carrier[0]) 00318 | (dsolution[1].proof & carrier[1]) 00319 | m_workBrd->GetPosition().GetColor(!color); 00320 solution.proof = solution.proof - m_workBrd->GetDead(); 00321 return false; 00322 } 00323 00324 /** Does the recursive mustplay search. */ 00325 bool DfsSolver::SolveInteriorState(PointSequence& variation, 00326 DfsSolutionSet& solution) 00327 { 00328 HexColor color = m_state->ToPlay(); 00329 int numstones = m_state->Position().NumStones(); 00330 // Set initial proof for this state to be the union of all 00331 // opponent winning semis. We need to do this because we use the 00332 // semis to restrict the search (ie, the mustplay). 00333 // Proof will also include all opponent stones. 00334 // 00335 // Basically, we are assuming the opponent will win from this state; 00336 // if we win instead, we use the proof generated from that state, 00337 // not this one. 00338 solution.proof = ProofUtil::InitialProofForOpponent(*m_workBrd, color); 00339 bitset_t mustplay = EndgameUtils::MovesToConsider(*m_workBrd, color); 00340 HexAssert(mustplay.any()); 00341 00342 int depth = variation.size(); 00343 if (m_use_guifx && depth == m_update_depth) 00344 { 00345 std::ostringstream os; 00346 os << "gogui-gfx:\n"; 00347 os << "solver\n"; 00348 os << "VAR"; 00349 HexColor toplay = (variation.size() & 1) ? !color : color; 00350 for (std::size_t i = 0; i < variation.size(); ++i) 00351 { 00352 os << ' ' << (toplay == BLACK ? 'B' : 'W'); 00353 os << ' ' << variation[i]; 00354 toplay = !toplay; 00355 } 00356 os << '\n'; 00357 os << "LABEL "; 00358 const InferiorCells& inf = m_workBrd->GetInferiorCells(); 00359 os << inf.GuiOutput(); 00360 os << BoardUtils::GuiDumpOutsideConsiderSet(m_workBrd->GetPosition(), 00361 mustplay, inf.All()); 00362 os << '\n'; 00363 os << "TEXT"; 00364 for (int i = 0; i < depth; ++i) 00365 os << ' ' << m_completed[i].first << '/' << m_completed[i].second; 00366 os << '\n'; 00367 os << '\n'; 00368 std::cout << os.str(); 00369 std::cout.flush(); 00370 } 00371 00372 bitset_t original_mustplay = mustplay; 00373 solution.stats.total_states = 1; 00374 solution.stats.explored_states = 1; 00375 solution.stats.minimal_explored = 1; 00376 solution.stats.expanded_states = 1; 00377 solution.stats.moves_to_consider = mustplay.count(); 00378 m_histogram.states[numstones]++; 00379 00380 // Order moves in the mustplay. 00381 // 00382 // NOTE: If we want to find all winning moves then we need to stop 00383 // OrderMoves() from aborting on a win. 00384 // 00385 // NOTE: OrderMoves() will handle VC/DB/TT hits, and remove them 00386 // from consideration. It is possible that there are no moves, in 00387 // which case we fall through the loop below with no problem (the 00388 // state is a loss). 00389 solution.m_numMoves = -1; 00390 std::vector<HexMoveValue> moves; 00391 bool winning_state = OrderMoves(mustplay, solution, moves); 00392 00393 //---------------------------------------------------------------------- 00394 // Expand all moves in mustplay that were not leaf states. 00395 //---------------------------------------------------------------------- 00396 std::size_t states_under_losing = 0; 00397 00398 for (unsigned index = 0; 00399 !winning_state && index < moves.size(); 00400 ++index) 00401 { 00402 HexPoint cell = moves[index].point(); 00403 m_completed[depth] = std::make_pair(index, moves.size()); 00404 if (!mustplay.test(cell)) 00405 { 00406 solution.stats.pruned++; 00407 continue; 00408 } 00409 00410 DfsSolutionSet child; 00411 PlayMove(cell); 00412 variation.push_back(cell); 00413 bool win = !SolveState(variation, child); 00414 variation.pop_back(); 00415 UndoMove(cell); 00416 solution.stats += child.stats; 00417 00418 if (win) 00419 { 00420 // Win: copy proof over, copy pv, abort! 00421 winning_state = true; 00422 solution.proof = child.proof; 00423 solution.SetPV(cell, child.pv); 00424 solution.m_numMoves = child.m_numMoves + 1; 00425 solution.stats.winning_expanded++; 00426 solution.stats.minimal_explored = child.stats.minimal_explored + 1; 00427 solution.stats.branches_to_win += index + 1; 00428 00429 m_histogram.winning[numstones]++; 00430 m_histogram.size_of_winning_states[numstones] 00431 += child.stats.explored_states; 00432 m_histogram.branches[numstones] += index + 1; 00433 m_histogram.states_under_losing[numstones] += states_under_losing; 00434 m_histogram.mustplay[numstones] += original_mustplay.count(); 00435 00436 HexAssert(solution.m_numMoves != -1); 00437 } 00438 else 00439 { 00440 // Loss: add returned proof to current proof. Prune 00441 // mustplay by proof. Maintain PV to longest loss. 00442 mustplay &= child.proof; 00443 solution.proof |= child.proof; 00444 states_under_losing += child.stats.explored_states; 00445 00446 m_histogram.size_of_losing_states[numstones] 00447 += child.stats.explored_states; 00448 00449 if (child.m_numMoves + 1 > solution.m_numMoves) 00450 { 00451 solution.m_numMoves = child.m_numMoves + 1; 00452 solution.SetPV(cell, child.pv); 00453 } 00454 HexAssert(solution.m_numMoves != -1); 00455 } 00456 } 00457 HexAssert(solution.m_numMoves != -1); 00458 return winning_state; 00459 } 00460 00461 /** Shrinks/verifies proof then stores it. */ 00462 void DfsSolver::HandleProof(const PointSequence& variation, 00463 bool winning_state, DfsSolutionSet& solution) 00464 { 00465 if (m_aborted) 00466 return; 00467 HexColor color = m_state->ToPlay(); 00468 HexColor winner = (winning_state) ? color : !color; 00469 HexColor loser = !winner; 00470 // Verify loser's stones do not intersect proof 00471 if ((m_workBrd->GetPosition().GetColor(loser) & solution.proof).any()) 00472 throw BenzeneException() 00473 << "DfsSolver::HandleProof:\n" 00474 << color << " to play.\n" 00475 << loser << " loses.\n" 00476 << "Losing stones hit proof:\n" 00477 << m_workBrd->Write(solution.proof) << '\n' 00478 << *m_workBrd << '\n' 00479 << "PV: " << HexPointUtil::ToString(variation) << '\n'; 00480 // Verify dead cells do not intersect proof 00481 if ((m_workBrd->GetDead() & solution.proof).any()) 00482 throw BenzeneException() 00483 << "DfsSolver::HandleProof:\n" 00484 << color << " to play.\n" 00485 << loser << " loses.\n" 00486 << "Dead cells hit proof:\n" 00487 << m_workBrd->Write(solution.proof) << '\n' 00488 << *m_workBrd << '\n' 00489 << "PV: " << HexPointUtil::ToString(variation) << '\n'; 00490 // Shrink proof 00491 bitset_t old_proof = solution.proof; 00492 if (m_shrink_proofs) 00493 { 00494 ProofUtil::ShrinkProof(solution.proof, m_state->Position(), loser, 00495 m_workBrd->ICE()); 00496 bitset_t pruned; 00497 pruned = BoardUtils::ReachableOnBitset(m_workBrd->Const(), 00498 solution.proof, 00499 EMPTY_BITSET, 00500 HexPointUtil::colorEdge1(winner)); 00501 pruned &= BoardUtils::ReachableOnBitset(m_workBrd->Const(), 00502 solution.proof, 00503 EMPTY_BITSET, 00504 HexPointUtil::colorEdge2(winner)); 00505 solution.proof = pruned; 00506 00507 if (solution.proof.count() < old_proof.count()) 00508 { 00509 solution.stats.shrunk++; 00510 solution.stats.cells_removed 00511 += old_proof.count() - solution.proof.count(); 00512 } 00513 } 00514 // Verify proof touches both of winner's edges 00515 if (!BoardUtils::ConnectedOnBitset(m_workBrd->Const(), solution.proof, 00516 HexPointUtil::colorEdge1(winner), 00517 HexPointUtil::colorEdge2(winner))) 00518 throw BenzeneException() 00519 << "DfsSolver::HandleProof:\n" 00520 << "Proof does not touch both edges!\n" 00521 << m_workBrd->Write(solution.proof) << '\n' 00522 << "Original proof:\n" 00523 << m_workBrd->Write(old_proof) << '\n' 00524 << *m_workBrd << '\n' 00525 << color << " to play.\n" 00526 << "PV: " << HexPointUtil::ToString(variation) << '\n'; 00527 00528 /** @todo HANDLE BEST MOVES PROPERLY! 00529 This can only happen if the mustplay goes empty in an internal 00530 state that wasn't determined initially, or in a decomp state 00531 where the fillin causes a terminal state. 00532 */ 00533 if (solution.pv.empty()) 00534 solution.pv.push_back(INVALID_POINT); 00535 00536 StoreState(DfsData(winning_state, solution.stats.total_states, 00537 solution.m_numMoves, solution.pv[0]), 00538 solution.proof); 00539 } 00540 00541 //---------------------------------------------------------------------------- 00542 00543 /** Plays the move; updates the board. */ 00544 void DfsSolver::PlayMove(HexPoint cell) 00545 { 00546 m_statistics.played++; 00547 m_workBrd->PlayMove(m_state->ToPlay(), cell); 00548 m_state->PlayMove(cell); 00549 00550 } 00551 00552 /** Takes back the move played. */ 00553 void DfsSolver::UndoMove(HexPoint cell) 00554 { 00555 m_state->UndoMove(cell); 00556 m_workBrd->UndoMove(); 00557 } 00558 00559 //---------------------------------------------------------------------------- 00560 00561 /** Orders the moves in mustplay using several heuristics. Aborts 00562 move ordering early if it finds a TT win: winning move is put to 00563 the front. 00564 00565 Will shrink the mustplay if it encounters TT losses: losing moves 00566 are not added to the list of sorted moves. 00567 00568 Returns true if it found a TT win, false otherwise. 00569 */ 00570 bool DfsSolver::OrderMoves(bitset_t& mustplay, DfsSolutionSet& solution, 00571 std::vector<HexMoveValue>& moves) 00572 { 00573 LogFine() << "OrderMoves\n"; 00574 HexColor color = m_state->ToPlay(); 00575 HexColor other = !color; 00576 00577 // union and intersection of proofs for all losing moves 00578 bitset_t proof_union; 00579 bitset_t proof_intersection; 00580 proof_intersection.set(); 00581 00582 /** The TT/DB checks are done as a single 1-ply sweep prior to any 00583 move ordering, since computing the VCs for any solved states 00584 is pointless, plus these may resolve the current state immediately. 00585 */ 00586 bool found_win = false; 00587 bitset_t losingMoves; 00588 for (BitsetIterator it(mustplay); !found_win && it; ++it) 00589 { 00590 m_workBrd->GetPosition().PlayMove(color, *it); 00591 m_state->PlayMove(*it); 00592 00593 DfsData data; 00594 if (CheckTransposition(data)) 00595 { 00596 solution.stats.explored_states += 1; 00597 solution.stats.minimal_explored++; 00598 solution.stats.total_states += data.m_numStates; 00599 00600 if (!data.m_win) 00601 { 00602 found_win = true; 00603 moves.clear(); 00604 moves.push_back(HexMoveValue(*it, 0)); 00605 00606 // This state plus the child winning state (which is a leaf). 00607 solution.stats.minimal_explored = 2; 00608 solution.proof = ProofUtil::MaximumProofSet(*m_workBrd, color); 00609 solution.m_numMoves = data.m_numMoves + 1; 00610 solution.SetPV(*it); 00611 } 00612 else 00613 { 00614 // Prune this losing move from the mustplay 00615 losingMoves.set(*it); 00616 if (data.m_numMoves + 1 > solution.m_numMoves) 00617 { 00618 solution.m_numMoves = data.m_numMoves + 1; 00619 solution.SetPV(*it); 00620 } 00621 // Will prune the mustplay later on with the proof 00622 bitset_t proof = ProofUtil::MaximumProofSet(*m_workBrd, !color); 00623 proof_intersection &= proof; 00624 proof_union |= proof; 00625 } 00626 } 00627 m_workBrd->GetPosition().UndoMove(*it); 00628 m_state->UndoMove(*it); 00629 } 00630 00631 if (found_win) 00632 { 00633 HexAssert(moves.size() == 1); 00634 LogFine() << "Found winning move; aborted ordering.\n"; 00635 return true; 00636 } 00637 00638 // We need to actually order moves now :) 00639 boost::scoped_ptr<Resistance> resist; 00640 bool with_ordering = m_move_ordering; 00641 bool with_resist = m_move_ordering & DfsMoveOrderFlags::WITH_RESIST; 00642 bool with_center = m_move_ordering & DfsMoveOrderFlags::FROM_CENTER; 00643 bool with_mustplay = m_move_ordering & DfsMoveOrderFlags::WITH_MUSTPLAY; 00644 00645 if (with_resist && with_ordering) 00646 { 00647 resist.reset(new Resistance()); 00648 resist->Evaluate(*m_workBrd); 00649 } 00650 00651 moves.clear(); 00652 for (BitsetIterator it(mustplay); !found_win && it; ++it) 00653 { 00654 bool skip_this_move = false; 00655 double score = 0.0; 00656 00657 // Skip losing moves found in DB/TT 00658 if (losingMoves.test(*it)) 00659 continue; 00660 00661 if (with_ordering) 00662 { 00663 double mustplay_size = 0.0; 00664 double fromcenter = 0.0; 00665 double rscore = 0.0; 00666 double tiebreaker = 0.0; 00667 bool exact_score = false; 00668 bool winning_semi_exists = false; 00669 00670 // Do mustplay move-ordering. This entails playing each 00671 // move, computing the vcs, storing the mustplay size, 00672 // then undoing the move. This gives pretty good move 00673 // ordering: 7x7 is much slower without this method and 00674 // 8x8 is no longer solvable. However, it is very expensive! 00675 if (with_mustplay) 00676 { 00677 PlayMove(*it); 00678 00679 DfsData data; 00680 bitset_t proof; 00681 // No need to check DB/TT since did this above 00682 if (HandleTerminalNode(data, proof)) 00683 { 00684 exact_score = true; 00685 solution.stats.minimal_explored++; 00686 solution.stats.explored_states++; 00687 solution.stats.total_states += data.m_numStates; 00688 if (!data.m_win) 00689 { 00690 found_win = true; 00691 moves.clear(); 00692 00693 // This state plus the child winning state 00694 // (which is a leaf). 00695 solution.stats.minimal_explored = 2; 00696 solution.proof = proof; 00697 solution.m_numMoves = data.m_numMoves + 1; 00698 solution.SetPV(*it); 00699 } 00700 else 00701 { 00702 skip_this_move = true; 00703 if (data.m_numMoves + 1 > solution.m_numMoves) 00704 { 00705 solution.m_numMoves = data.m_numMoves + 1; 00706 solution.SetPV(*it); 00707 } 00708 // Will prune the mustplay with the proof below 00709 proof_intersection &= proof; 00710 proof_union |= proof; 00711 } 00712 } 00713 else 00714 { 00715 // Not a leaf node. 00716 // Do we force a mustplay on our opponent? 00717 HexPoint edge1 = HexPointUtil::colorEdge1(color); 00718 HexPoint edge2 = HexPointUtil::colorEdge2(color); 00719 if (m_workBrd->Cons(color).Exists(edge1, edge2, VC::SEMI)) 00720 winning_semi_exists = true; 00721 bitset_t mp = VCUtils::GetMustplay(*m_workBrd, other); 00722 mustplay_size = mp.count(); 00723 } 00724 00725 UndoMove(*it); 00726 } // end of mustplay move ordering 00727 00728 // Perform move ordering 00729 if (!exact_score) 00730 { 00731 if (with_center) 00732 { 00733 fromcenter 00734 += DfsSolverUtil::DistanceFromCenter(m_workBrd->Const(), *it); 00735 } 00736 if (with_resist) 00737 { 00738 rscore = resist->Score(*it); 00739 HexAssert(rscore < 100.0); 00740 } 00741 tiebreaker = (with_resist) ? 100.0 - rscore : fromcenter; 00742 00743 if (winning_semi_exists) 00744 score = 1000.0 * mustplay_size + tiebreaker; 00745 else 00746 score = 1000000.0 * tiebreaker; 00747 } 00748 } 00749 if (!skip_this_move) 00750 moves.push_back(HexMoveValue(*it, score)); 00751 } 00752 HexAssert(!found_win || moves.size() == 1); 00753 // NOTE: sort() is not stable, so multiple runs can produce 00754 // different move orders in the same state unless stable_sort() is 00755 // used. 00756 stable_sort(moves.begin(), moves.end()); 00757 00758 // For a win: nothing to do 00759 if (found_win) 00760 LogFine() << "Found winning move; aborted ordering.\n"; 00761 // For a loss: recompute mustplay because backed-up ice info could 00762 // shrink it. Then prune with the intersection of all losing 00763 // proofs, and add in the union of all losing proofs to the 00764 // current proof. 00765 else 00766 { 00767 if (m_backup_ice_info) 00768 { 00769 bitset_t new_initial_proof 00770 = ProofUtil::InitialProofForOpponent(*m_workBrd, color); 00771 bitset_t new_mustplay 00772 = EndgameUtils::MovesToConsider(*m_workBrd, color); 00773 HexAssert(BitsetUtil::IsSubsetOf(new_mustplay, mustplay)); 00774 00775 if (new_mustplay.count() < mustplay.count()) 00776 { 00777 LogFine() << "Pruned mustplay with backing-up info." 00778 << m_workBrd->Write(mustplay) 00779 << m_workBrd->Write(new_mustplay) << '\n'; 00780 mustplay = new_mustplay; 00781 solution.proof = new_initial_proof; 00782 } 00783 } 00784 mustplay &= proof_intersection; 00785 solution.proof |= proof_union; 00786 } 00787 return found_win; 00788 } 00789 00790 //---------------------------------------------------------------------------- 00791 00792 std::string DfsHistogram::Write() 00793 { 00794 std::ostringstream os; 00795 os << '\n' 00796 << "Histogram\n" 00797 << " States " 00798 << " Branch Info " 00799 << " TT/DB " 00800 << '\n' 00801 << std::setw(3) << "#" << " " 00802 << std::setw(12) << "Terminal" 00803 << std::setw(12) << "Internal" 00804 << std::setw(12) << "Int. Win" 00805 << std::setw(12) << "Win Pct" 00806 << std::setw(12) << "Sz Winning" 00807 << std::setw(12) << "Sz Losing" 00808 << std::setw(12) << "To Win" 00809 << std::setw(12) << "Mustplay" 00810 << std::setw(12) << "U/Losing" 00811 << std::setw(12) << "Cost" 00812 << std::setw(12) << "Hits" 00813 << std::setw(12) << "Pct" 00814 << '\n'; 00815 00816 for (int p = 0; p < FIRST_INVALID; ++p) 00817 { 00818 if (!states[p] && !terminal[p]) 00819 continue; 00820 double moves_to_find_winning = winning[p] 00821 ? (double)branches[p] / winning[p] : 0; 00822 double avg_states_under_losing = (branches[p] - winning[p]) 00823 ?((double)states_under_losing[p] / (branches[p] - winning[p])):0; 00824 os << std::setw(3) << p << ":" 00825 << std::setw(12) << terminal[p] 00826 << std::setw(12) << states[p] 00827 << std::setw(12) << winning[p] 00828 << std::setw(12) << std::fixed << std::setprecision(3) 00829 << ((states[p])?((double)winning[p]*100.0/states[p]):0) 00830 << std::setw(12) << std::fixed << std::setprecision(1) 00831 << ((winning[p]) 00832 ? ((double)size_of_winning_states[p] / winning[p]) 00833 : 0) 00834 << std::setw(12) << std::fixed << std::setprecision(1) 00835 << ((states[p] - winning[p]) 00836 ? ((double)(size_of_losing_states[p] 00837 / (states[p] - winning[p]))) 00838 :0) 00839 << std::setw(12) << std::fixed << std::setprecision(4) 00840 << moves_to_find_winning 00841 << std::setw(12) << std::fixed << std::setprecision(2) 00842 << ((winning[p]) ? ((double)mustplay[p] / winning[p]) : 0) 00843 << std::setw(12) << std::fixed << std::setprecision(1) 00844 << avg_states_under_losing 00845 << std::setw(12) << std::fixed << std::setprecision(1) 00846 << fabs((moves_to_find_winning - 1.0) 00847 * avg_states_under_losing * winning[p]) 00848 << std::setw(12) << tthits[p] 00849 << std::setw(12) << std::fixed << std::setprecision(3) 00850 << ((states[p]) ? ((double)tthits[p] * 100.0 / states[p]) : 0) 00851 << '\n'; 00852 } 00853 return os.str(); 00854 } 00855 00856 void DfsSolver::DumpStats(const DfsSolutionSet& solution) const 00857 { 00858 const double total_time = m_end_time - m_start_time; 00859 std::ostringstream os; 00860 os << '\n' 00861 << "Played " << m_statistics.played << '\n' 00862 << "Pruned " << solution.stats.pruned << '\n' 00863 << "Total States " << solution.stats.total_states << '\n' 00864 << "Explored States " << solution.stats.explored_states 00865 << " (" << solution.stats.minimal_explored << ")" << '\n' 00866 << "Expanded States " << solution.stats.expanded_states << '\n' 00867 << "Decompositions " << solution.stats.decompositions << '\n' 00868 << "Decomps won " << solution.stats.decompositions_won << '\n' 00869 << "Shrunk Proofs " << solution.stats.shrunk << '\n' 00870 << "Avg. Shrink " << ((double)solution.stats.cells_removed 00871 / solution.stats.shrunk) << '\n' 00872 << "Branch Factor " << ((double)solution.stats.moves_to_consider 00873 / solution.stats.expanded_states) << '\n' 00874 << "To Find Win " << ((double)solution.stats.branches_to_win 00875 / solution.stats.winning_expanded) << '\n' 00876 << "States/sec " << (solution.stats.explored_states 00877 / total_time) << '\n' 00878 << "Played/sec " << (m_statistics.played/total_time) << '\n' 00879 << "Total Time " << Time::Formatted(total_time) << '\n' 00880 << "Moves to W/L " << solution.m_numMoves << " moves\n" 00881 << "PV " << HexPointUtil::ToString(solution.pv) << '\n'; 00882 if (m_positions->Database()) 00883 os << '\n' << m_positions->Database()->GetStatistics().Write() << '\n'; 00884 if (m_positions->HashTable()) 00885 os << '\n' << m_positions->HashTable()->Stats(); 00886 LogInfo() << os.str(); 00887 } 00888 00889 //---------------------------------------------------------------------------- 00890 00891 int DfsSolverUtil::DistanceFromCenter(const ConstBoard& brd, HexPoint cell) 00892 { 00893 // Odd boards are easy 00894 if ((brd.Width() & 1) && (brd.Height() & 1)) 00895 return brd.Distance(BoardUtils::CenterPoint(brd), cell); 00896 00897 // Make sure we spiral nicely on boards with even 00898 // dimensions. Take the sum of the distance between 00899 // the two center cells on the main diagonal. 00900 return brd.Distance(BoardUtils::CenterPointRight(brd), cell) 00901 + brd.Distance(BoardUtils::CenterPointLeft(brd), cell); 00902 } 00903 00904 //----------------------------------------------------------------------------