Main   Namespaces   Classes   Hierarchy   Annotated   Files   Compound   Global   Pages  

DfsSolver.cpp

Go to the documentation of this file.
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 //----------------------------------------------------------------------------


6 Jan 2011 Doxygen 1.6.3