00001 //---------------------------------------------------------------------------- 00002 /** @file SgBookBuilder.cpp 00003 */ 00004 //---------------------------------------------------------------------------- 00005 00006 #include "SgBookBuilder.h" 00007 #include "SgDebug.h" 00008 #include "SgPoint.h" 00009 #include "SgTimer.h" 00010 #include <sstream> 00011 #include <boost/numeric/conversion/bounds.hpp> 00012 00013 //---------------------------------------------------------------------------- 00014 00015 bool SgBookNode::IsTerminal() const 00016 { 00017 if (m_value < -100 || m_value > 100) 00018 return true; 00019 return false; 00020 } 00021 00022 bool SgBookNode::IsLeaf() const 00023 { 00024 return m_count == 0; 00025 } 00026 00027 std::string SgBookNode::ToString() const 00028 { 00029 std::ostringstream os; 00030 os << std::showpos << std::fixed << std::setprecision(6); 00031 os << "Val " << m_value; 00032 os << std::noshowpos << " ExpP " << m_priority; 00033 os << std::showpos << " Heur " << m_heurValue << " Cnt " << m_count; 00034 return os.str(); 00035 } 00036 00037 void SgBookNode::FromString(const std::string& str) 00038 { 00039 std::istringstream is(str); 00040 std::string dummy; 00041 is >> dummy; 00042 is >> m_value; 00043 is >> dummy; 00044 is >> m_priority; 00045 is >> dummy; 00046 is >> m_heurValue; 00047 is >> dummy; 00048 is >> m_count; 00049 } 00050 00051 //---------------------------------------------------------------------------- 00052 00053 SgBookBuilder::SgBookBuilder() 00054 : m_alpha(50), 00055 m_use_widening(true), 00056 m_expand_width(16), 00057 m_expand_threshold(1000), 00058 m_flush_iterations(100) 00059 { 00060 } 00061 00062 SgBookBuilder::~SgBookBuilder() 00063 { 00064 } 00065 00066 //---------------------------------------------------------------------------- 00067 00068 void SgBookBuilder::StartIteration(int iteration) 00069 { 00070 SG_UNUSED(iteration); 00071 // DEFAULT IMPLEMENTATION DOES NOTHING 00072 } 00073 00074 void SgBookBuilder::EndIteration() 00075 { 00076 // DEFAULT IMPLEMENTATION DOES NOTHING 00077 } 00078 00079 void SgBookBuilder::BeforeEvaluateChildren() 00080 { 00081 // DEFAULT IMPLEMENTATION DOES NOTHING 00082 } 00083 00084 void SgBookBuilder::AfterEvaluateChildren() 00085 { 00086 // DEFAULT IMPLEMENTATION DOES NOTHING 00087 } 00088 00089 void SgBookBuilder::Init() 00090 { 00091 // DEFAULT IMPLEMENTATION DOES NOTHING 00092 } 00093 00094 void SgBookBuilder::Fini() 00095 { 00096 // DEFAULT IMPLEMENTATION DOES NOTHING 00097 } 00098 00099 void SgBookBuilder::Expand(int numExpansions) 00100 { 00101 m_num_evals = 0; 00102 m_num_widenings = 0; 00103 00104 SgTimer timer; 00105 Init(); 00106 EnsureRootExists(); 00107 int num = 0; 00108 for (; num < numExpansions; ++num) 00109 { 00110 StartIteration(num); 00111 // If root position becomes a known win or loss, then there's 00112 // no point in continuing to expand the opening book. 00113 { 00114 SgBookNode root; 00115 GetNode(root); 00116 if (root.IsTerminal()) 00117 { 00118 PrintMessage("Root is terminal!\n"); 00119 break; 00120 } 00121 } 00122 std::vector<SgMove> pv; 00123 DoExpansion(pv); 00124 EndIteration(); 00125 // Flush the book if we've performed enough iterations 00126 if (num && (num % m_flush_iterations) == 0) 00127 FlushBook(); 00128 } 00129 FlushBook(); 00130 Fini(); 00131 timer.Stop(); 00132 double elapsed = timer.GetTime(); 00133 std::ostringstream os; 00134 os << '\n' 00135 << "Statistics\n" 00136 << "Total Time " << elapsed << '\n' 00137 << "Expansions " << num 00138 << std::fixed << std::setprecision(2) 00139 << " (" << (num / elapsed) << "/s)\n" 00140 << "Evaluations " << m_num_evals 00141 << std::fixed << std::setprecision(2) 00142 << " (" << (m_num_evals / elapsed) << "/s)\n" 00143 << "Widenings " << m_num_widenings << '\n'; 00144 PrintMessage(os.str()); 00145 } 00146 00147 void SgBookBuilder::Refresh() 00148 { 00149 m_num_evals = 0; 00150 m_num_widenings = 0; 00151 m_value_updates = 0; 00152 m_priority_updates = 0; 00153 m_internal_nodes = 0; 00154 m_leaf_nodes = 0; 00155 m_terminal_nodes = 0; 00156 00157 SgTimer timer; 00158 Init(); 00159 ClearAllVisited(); 00160 Refresh(true); 00161 FlushBook(); 00162 Fini(); 00163 timer.Stop(); 00164 00165 double elapsed = timer.GetTime(); 00166 std::ostringstream os; 00167 os << '\n' 00168 << "Statistics\n" 00169 << "Total Time " << elapsed << '\n' 00170 << "Value Updates " << m_value_updates << '\n' 00171 << "Priority Updates " << m_priority_updates << '\n' 00172 << "Internal Nodes " << m_internal_nodes << '\n' 00173 << "Terminal Nodes " << m_terminal_nodes << '\n' 00174 << "Leaf Nodes " << m_leaf_nodes << '\n' 00175 << "Evaluations " << m_num_evals 00176 << std::fixed << std::setprecision(2) 00177 << " (" << (m_num_evals / elapsed) << "/s)\n" 00178 << "Widenings " << m_num_widenings << '\n'; 00179 PrintMessage(os.str()); 00180 } 00181 00182 void SgBookBuilder::IncreaseWidth() 00183 { 00184 if (!m_use_widening) 00185 { 00186 PrintMessage("Widening not enabled!\n"); 00187 return; 00188 } 00189 00190 m_num_evals = 0; 00191 m_num_widenings = 0; 00192 00193 SgTimer timer; 00194 Init(); 00195 ClearAllVisited(); 00196 IncreaseWidth(true); 00197 FlushBook(); 00198 Fini(); 00199 timer.Stop(); 00200 double elapsed = timer.GetTime(); 00201 00202 std::ostringstream os; 00203 os << '\n' 00204 << "Statistics\n" 00205 << "Total Time " << elapsed << '\n' 00206 << "Widenings " << m_num_widenings << '\n' 00207 << "Evaluations " << m_num_evals 00208 << std::fixed << std::setprecision(2) 00209 << " (" << (m_num_evals / elapsed) << "/s)\n"; 00210 PrintMessage(os.str()); 00211 } 00212 00213 //---------------------------------------------------------------------------- 00214 00215 /** Creates a node for each of the leaf's first count children that 00216 have not been created yet. Returns true if at least one new node 00217 was created, false otherwise. */ 00218 bool SgBookBuilder::ExpandChildren(std::size_t count) 00219 { 00220 // It is possible the state is determined, even though it was 00221 // already evaluated. This can happen in Hex: it is not very likey 00222 // if the evaluation function is reasonably heavyweight, but if 00223 // just using fillin and vcs, it is possible that the fillin 00224 // prevents a winning vc from being created. 00225 float value = 0; 00226 std::vector<SgMove> children; 00227 if (GenerateMoves(children, value)) 00228 { 00229 PrintMessage("ExpandChildren: State is determined!\n"); 00230 WriteNode(SgBookNode(value)); 00231 return false; 00232 } 00233 std::size_t limit = std::min(count, children.size()); 00234 std::vector<SgMove> childrenToDo; 00235 for (std::size_t i = 0; i < limit; ++i) 00236 { 00237 PlayMove(children[i]); 00238 SgBookNode child; 00239 if (!GetNode(child)) 00240 childrenToDo.push_back(children[i]); 00241 UndoMove(children[i]); 00242 } 00243 if (!childrenToDo.empty()) 00244 { 00245 BeforeEvaluateChildren(); 00246 std::vector<std::pair<SgMove, float> > scores; 00247 EvaluateChildren(childrenToDo, scores); 00248 AfterEvaluateChildren(); 00249 for (std::size_t i = 0; i < scores.size(); ++i) 00250 { 00251 PlayMove(scores[i].first); 00252 WriteNode(scores[i].second); 00253 UndoMove(scores[i].first); 00254 } 00255 m_num_evals += childrenToDo.size(); 00256 return true; 00257 } 00258 else 00259 PrintMessage("Children already evaluated.\n"); 00260 return false; 00261 } 00262 00263 std::size_t SgBookBuilder::NumChildren(const std::vector<SgMove>& legal) 00264 { 00265 std::size_t num = 0; 00266 for (size_t i = 0; i < legal.size(); ++i) 00267 { 00268 PlayMove(legal[i]); 00269 SgBookNode child; 00270 if (GetNode(child)) 00271 ++num; 00272 UndoMove(legal[i]); 00273 } 00274 return num; 00275 } 00276 00277 void SgBookBuilder::UpdateValue(SgBookNode& node, 00278 const std::vector<SgMove>& legal) 00279 { 00280 bool hasChild = false; 00281 float bestValue = boost::numeric::bounds<float>::lowest(); 00282 for (std::size_t i = 0; i < legal.size(); ++i) 00283 { 00284 PlayMove(legal[i]); 00285 SgBookNode child; 00286 if (GetNode(child)) 00287 { 00288 hasChild = true; 00289 float value = InverseEval(Value(child)); 00290 if (value > bestValue) 00291 bestValue = value; 00292 } 00293 UndoMove(legal[i]); 00294 } 00295 if (hasChild) 00296 node.m_value = bestValue; 00297 } 00298 00299 /** Updates the node's value, taking special care if the value is a 00300 loss. In this case, widenings are performed until a non-loss child 00301 is added or no new children are added. The node is then set with 00302 the proper value. */ 00303 void SgBookBuilder::UpdateValue(SgBookNode& node) 00304 { 00305 while (true) 00306 { 00307 std::vector<SgMove> legal; 00308 GetAllLegalMoves(legal); 00309 UpdateValue(node, legal); 00310 if (!IsLoss(Value(node))) 00311 break; 00312 // Round up to next nearest multiple of m_expand_width that is 00313 // larger than the current number of children. 00314 std::size_t numChildren = NumChildren(legal); 00315 std::size_t width = (numChildren / m_expand_width + 1) 00316 * m_expand_width; 00317 { 00318 std::ostringstream os; 00319 os << "Forced Widening[" << numChildren << "->" << width << "]\n"; 00320 PrintMessage(os.str()); 00321 } 00322 if (!ExpandChildren(width)) 00323 break; 00324 ++m_num_widenings; 00325 } 00326 } 00327 00328 float SgBookBuilder::ComputePriority(const SgBookNode& parent, 00329 const float childValue, 00330 const float childPriority) const 00331 { 00332 float delta = parent.m_value - InverseEval(childValue); 00333 SG_ASSERT(delta >= 0.0); 00334 return m_alpha * delta + childPriority + 1; 00335 } 00336 00337 /** Re-computes node's priority and returns the best child to 00338 expand. Requires that UpdateValue() has been called on this 00339 node. Returns SG_NULLMOVE if node has no children. */ 00340 SgMove SgBookBuilder::UpdatePriority(SgBookNode& node) 00341 { 00342 bool hasChild = false; 00343 float bestPriority = boost::numeric::bounds<float>::highest(); 00344 SgMove bestChild = SG_NULLMOVE; 00345 std::vector<SgMove> legal; 00346 GetAllLegalMoves(legal); 00347 for (std::size_t i = 0; i < legal.size(); ++i) 00348 { 00349 PlayMove(legal[i]); 00350 SgBookNode child; 00351 if (GetNode(child)) 00352 { 00353 hasChild = true; 00354 // Must adjust child value for swap, but not the parent 00355 // because we are comparing with the best child's value, 00356 // ie, the minmax value. 00357 float childValue = Value(child); 00358 float childPriority = child.m_priority; 00359 float priority = ComputePriority(node, childValue, childPriority); 00360 if (priority < bestPriority) 00361 { 00362 bestPriority = priority; 00363 bestChild = legal[i]; 00364 } 00365 } 00366 UndoMove(legal[i]); 00367 } 00368 if (hasChild) 00369 node.m_priority = bestPriority; 00370 return bestChild; 00371 } 00372 00373 void SgBookBuilder::DoExpansion(std::vector<SgMove>& pv) 00374 { 00375 SgBookNode node; 00376 if (!GetNode(node)) 00377 SG_ASSERT(false); 00378 if (node.IsTerminal()) 00379 return; 00380 if (node.IsLeaf()) 00381 { 00382 ExpandChildren(m_expand_width); 00383 } 00384 else 00385 { 00386 // Widen this non-terminal non-leaf node if necessary 00387 if (m_use_widening && (node.m_count % m_expand_threshold == 0)) 00388 { 00389 std::size_t width = (node.m_count / m_expand_threshold + 1) 00390 * m_expand_width; 00391 ++m_num_widenings; 00392 ExpandChildren(width); 00393 } 00394 // Compute value and priority. It's possible this node is newly 00395 // terminal if one of its new children is a winning move. 00396 GetNode(node); 00397 UpdateValue(node); 00398 SgMove mostUrgent = UpdatePriority(node); 00399 WriteNode(node); 00400 00401 // Recurse on most urgent child only if non-terminal. 00402 if (!node.IsTerminal()) 00403 { 00404 PlayMove(mostUrgent); 00405 pv.push_back(mostUrgent); 00406 DoExpansion(pv); 00407 pv.pop_back(); 00408 UndoMove(mostUrgent); 00409 } 00410 } 00411 GetNode(node); 00412 UpdateValue(node); 00413 UpdatePriority(node); 00414 node.IncrementCount(); 00415 WriteNode(node); 00416 } 00417 00418 //---------------------------------------------------------------------------- 00419 00420 /** Refresh's each child of the given state. UpdateValue() and 00421 UpdatePriority() are called on internal nodes. Returns true if 00422 state exists in book. 00423 @ref bookrefresh 00424 */ 00425 bool SgBookBuilder::Refresh(bool root) 00426 { 00427 if (HasBeenVisited()) 00428 return true; 00429 MarkAsVisited(); 00430 SgBookNode node; 00431 if (!GetNode(node)) 00432 return false; 00433 if (node.IsLeaf()) 00434 { 00435 m_leaf_nodes++; 00436 if (node.IsTerminal()) 00437 m_terminal_nodes++; 00438 return true; 00439 } 00440 double oldValue = Value(node); 00441 double oldPriority = node.m_priority; 00442 std::vector<SgMove> legal; 00443 GetAllLegalMoves(legal); 00444 for (std::size_t i = 0; i < legal.size(); ++i) 00445 { 00446 PlayMove(legal[i]); 00447 Refresh(false); 00448 if (root) 00449 { 00450 std::ostringstream os; 00451 os << "Finished " << SgWritePoint(legal[i]) << '\n'; 00452 PrintMessage(os.str()); 00453 } 00454 UndoMove(legal[i]); 00455 } 00456 UpdateValue(node); 00457 UpdatePriority(node); 00458 if (fabs(oldValue - Value(node)) > 0.0001) 00459 m_value_updates++; 00460 if (fabs(oldPriority - node.m_priority) > 0.0001) 00461 m_priority_updates++; 00462 WriteNode(node); 00463 if (node.IsTerminal()) 00464 m_terminal_nodes++; 00465 else 00466 m_internal_nodes++; 00467 return true; 00468 } 00469 00470 //---------------------------------------------------------------------------- 00471 00472 void SgBookBuilder::IncreaseWidth(bool root) 00473 { 00474 if (HasBeenVisited()) 00475 return; 00476 MarkAsVisited(); 00477 SgBookNode node; 00478 if (!GetNode(node)) 00479 return; 00480 if (node.IsTerminal() || node.IsLeaf()) 00481 return; 00482 std::vector<SgMove> legal; 00483 GetAllLegalMoves(legal); 00484 for (std::size_t i = 0; i < legal.size(); ++i) 00485 { 00486 PlayMove(legal[i]); 00487 IncreaseWidth(false); 00488 if (root) 00489 { 00490 std::ostringstream os; 00491 os << "Finished " << SgWritePoint(legal[i]) << '\n'; 00492 PrintMessage(os.str()); 00493 } 00494 UndoMove(legal[i]); 00495 } 00496 std::size_t width = (node.m_count / m_expand_threshold + 1) 00497 * m_expand_width; 00498 if (ExpandChildren(width)) 00499 ++m_num_widenings; 00500 } 00501 00502 //----------------------------------------------------------------------------