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