size_t DfpnSolver::MID(const DfpnBounds& maxBounds, DfpnHistory& history) { maxBounds.CheckConsistency(); SG_ASSERT(maxBounds.phi > 1); SG_ASSERT(maxBounds.delta > 1); ++m_numMIDcalls; size_t prevWork = 0; SgEmptyBlackWhite colorToMove = GetColorToMove(); DfpnData data; if (TTRead(data)) { prevWork = data.m_work; if (! maxBounds.GreaterThan(data.m_bounds)) // Estimated bounds are larger than we had // anticipated. The calling state must have computed // the max bounds with out of date information, so just // return here without doing anything: the caller will // now update to this new info and carry on. return 0; } else { SgEmptyBlackWhite winner = SG_EMPTY; if (TerminalState(colorToMove, winner)) { ++m_numTerminal; DfpnBounds terminal; if (colorToMove == winner) DfpnBounds::SetToWinning(terminal); else { SG_ASSERT(SgOppBW(colorToMove) == winner); DfpnBounds::SetToLosing(terminal); } TTWrite(DfpnData(terminal, SG_NULLMOVE, 1)); return 1; } } ++m_generateMoves; DfpnChildren children; GenerateChildren(children.Children()); // Not thread safe: perhaps move into while loop below later... std::vector<DfpnData> childrenData(children.Size()); for (size_t i = 0; i < children.Size(); ++i) LookupData(childrenData[i], children, i); // Index used for progressive widening size_t maxChildIndex = ComputeMaxChildIndex(childrenData); SgHashCode currentHash = Hash(); SgMove bestMove = SG_NULLMOVE; DfpnBounds currentBounds; size_t localWork = 1; do { UpdateBounds(currentBounds, childrenData, maxChildIndex); if (! maxBounds.GreaterThan(currentBounds)) break; // Select most proving child std::size_t bestIndex = 999999; DfpnBoundType delta2 = DfpnBounds::INFTY; SelectChild(bestIndex, delta2, childrenData, maxChildIndex); bestMove = children.MoveAt(bestIndex); // Compute maximum bound for child const DfpnBounds childBounds(childrenData[bestIndex].m_bounds); DfpnBounds childMaxBounds; childMaxBounds.phi = maxBounds.delta - (currentBounds.delta - childBounds.phi); childMaxBounds.delta = delta2 == DfpnBounds::INFTY ? maxBounds.phi : std::min(maxBounds.phi, std::max(delta2 + 1, DfpnBoundType(delta2 * (1.0 + m_epsilon)))); SG_ASSERT(childMaxBounds.GreaterThan(childBounds)); if (delta2 != DfpnBounds::INFTY) m_deltaIncrease.Add(float(childMaxBounds.delta-childBounds.delta)); // Recurse on best child PlayMove(bestMove); history.Push(bestMove, currentHash); localWork += MID(childMaxBounds, history); history.Pop(); UndoMove(); // Update bounds for best child LookupData(childrenData[bestIndex], children, bestIndex); // Compute some stats when find winning move if (childrenData[bestIndex].m_bounds.IsLosing()) { m_moveOrderingIndex.Add(float(bestIndex)); m_moveOrderingPercent.Add(float(bestIndex) / (float)childrenData.size()); m_totalWastedWork += prevWork + localWork - childrenData[bestIndex].m_work; } else if (childrenData[bestIndex].m_bounds.IsWinning()) maxChildIndex = ComputeMaxChildIndex(childrenData); } while (! CheckAbort()); // Find the most delaying move for losing states, and the smallest // winning move for winning states. if (currentBounds.IsSolved()) { if (currentBounds.IsLosing()) { std::size_t maxWork = 0; for (std::size_t i = 0; i < children.Size(); ++i) { if (childrenData[i].m_work > maxWork) { maxWork = childrenData[i].m_work; bestMove = children.MoveAt(i); } } } else { std::size_t minWork = DfpnBounds::INFTY; for (std::size_t i = 0; i < children.Size(); ++i) { if (childrenData[i].m_bounds.IsLosing() && childrenData[i].m_work < minWork) { minWork = childrenData[i].m_work; bestMove = children.MoveAt(i); } } } } // Store search results TTWrite(DfpnData(currentBounds, bestMove, localWork + prevWork)); return localWork; }
size_t DfpnSolver::MID(const DfpnBounds& maxBounds, DfpnHistory& history) { maxBounds.CheckConsistency(); HexAssert(maxBounds.phi > 1); HexAssert(maxBounds.delta > 1); int depth = history.Depth(); size_t prevWork = 0; bitset_t maxProofSet; float evaluationScore; HexColor colorToMove = m_state->ToPlay(); DfpnChildren children; { DfpnData data; if (TTRead(*m_state, data)) { children = data.m_children; maxProofSet = data.m_maxProofSet; prevWork = data.m_work; evaluationScore = data.m_evaluationScore; if (!maxBounds.GreaterThan(data.m_bounds)) // Estimated bounds are larger than we had // anticipated. The calling state must have computed // the max bounds with out of date information, so just // return here without doing anything: the caller will // now update to this new info and carry on. return 0; } else { m_workBoard->GetPosition().SetPosition(m_state->Position()); m_workBoard->ComputeAll(colorToMove); ++m_numVCbuilds; // Compute the maximum possible proof set if colorToMove wins. // This data is used to prune siblings of this state. maxProofSet = ProofUtil::MaximumProofSet(*m_workBoard, colorToMove); if (EndgameUtils::IsDeterminedState(*m_workBoard, colorToMove)) { ++m_numTerminal; DfpnBounds terminal; if (EndgameUtils::IsWonGame(*m_workBoard, colorToMove)) DfpnBounds::SetToWinning(terminal); else DfpnBounds::SetToLosing(terminal); if (m_useGuiFx && depth == 1) { m_guiFx.UpdateCurrentBounds(terminal); m_guiFx.Write(); } TTWrite(*m_state, DfpnData(terminal, DfpnChildren(), INVALID_POINT, 1, maxProofSet, 0.0)); return 1; } bitset_t childrenBitset = EndgameUtils::MovesToConsider(*m_workBoard, colorToMove); m_considerSetSize.Add(childrenBitset.count()); Resistance resist; resist.Evaluate(*m_workBoard); evaluationScore = (colorToMove == BLACK) ? resist.Score() : -resist.Score(); m_allEvaluation.Add(evaluationScore); std::vector<std::pair<HexEval, HexPoint> > mvsc; for (BitsetIterator it(childrenBitset); it; ++it) { HexEval score = resist.Score(*it); mvsc.push_back(std::make_pair(-score, *it)); } stable_sort(mvsc.begin(), mvsc.end()); std::vector<HexPoint> sortedChildren; for (size_t i = 0; i < mvsc.size(); ++i) sortedChildren.push_back(mvsc[i].second); children.SetChildren(sortedChildren); } } ++m_numMIDcalls; size_t localWork = 1; // Not thread safe: perhaps move into while loop below later... std::vector<DfpnData> childrenData(children.Size()); for (size_t i = 0; i < children.Size(); ++i) LookupData(childrenData[i], children, i, *m_state); // Index used for progressive widening size_t maxChildIndex = ComputeMaxChildIndex(childrenData); if (m_useGuiFx && depth == 0) m_guiFx.SetChildren(children, childrenData); hash_t currentHash = m_state->Hash(); HexPoint bestMove = INVALID_POINT; DfpnBounds currentBounds; do { UpdateBounds(currentBounds, childrenData, maxChildIndex); if (m_useGuiFx && depth == 1) { m_guiFx.UpdateCurrentBounds(currentBounds); m_guiFx.Write(); } if (!maxBounds.GreaterThan(currentBounds)) break; // Select most proving child int bestIndex = -1; DfpnBoundType delta2 = DfpnBounds::INFTY; SelectChild(bestIndex, delta2, childrenData, maxChildIndex); bestMove = children.FirstMove(bestIndex); // Compute maximum bound for child const DfpnBounds childBounds(childrenData[bestIndex].m_bounds); DfpnBounds childMaxBounds; childMaxBounds.phi = maxBounds.delta - (currentBounds.delta - childBounds.phi); childMaxBounds.delta = std::min(maxBounds.phi, delta2 + 1); HexAssert(childMaxBounds.GreaterThan(childBounds)); if (delta2 != DfpnBounds::INFTY) m_deltaIncrease.Add(childMaxBounds.delta - childBounds.delta); // Recurse on best child if (m_useGuiFx && depth == 0) m_guiFx.PlayMove(colorToMove, bestIndex); children.PlayMove(bestIndex, *m_state); history.Push(bestMove, currentHash); localWork += MID(childMaxBounds, history); history.Pop(); children.UndoMove(bestIndex, *m_state); if (m_useGuiFx && depth == 0) m_guiFx.UndoMove(); // Update bounds for best child LookupData(childrenData[bestIndex], children, bestIndex, *m_state); // Compute some stats when find winning move if (childrenData[bestIndex].m_bounds.IsLosing()) { m_moveOrderingIndex.Add(bestIndex); m_moveOrderingPercent.Add(bestIndex / (double)childrenData.size()); m_totalWastedWork += prevWork + localWork - childrenData[bestIndex].m_work; } else if (childrenData[bestIndex].m_bounds.IsWinning()) maxChildIndex = ComputeMaxChildIndex(childrenData); // Shrink children list using knowledge of bestMove child's proof set. // That is, if this child is losing, conclude what other children // must also be losing (i.e. cannot interfere with the proof set // that disproves this child). // And of course if this child is winning, no need to explore // these other siblings either. { /* @todo Perhaps track allChildren instead of recomputing? */ bitset_t allChildren; for (std::vector<HexPoint>::iterator it = children.m_children.begin(); it != children.m_children.end(); ++it) { allChildren.set(*it); } bitset_t canPrune = allChildren - childrenData[bestIndex].m_maxProofSet; canPrune.reset(bestMove); int pruneCount = canPrune.count(); if (pruneCount) { m_prunedSiblingStats.Add(pruneCount); /* LogInfo() << "Pruning " << pruneCount << " moves via " << bestMove << ".\nChildren:\n" << m_brd->Write(allChildren) << "\nRemoving...\n" << m_brd->Write(canPrune) << "\n"; */ DeleteChildren(children, childrenData, canPrune); maxChildIndex = ComputeMaxChildIndex(childrenData); if (m_useGuiFx && depth == 0) m_guiFx.SetChildren(children, childrenData); } } } while (!CheckAbort()); if (m_useGuiFx && depth == 0) m_guiFx.WriteForced(); // Find the most delaying move for losing states, and the smallest // winning move for winning states. if (currentBounds.IsSolved()) { m_allSolvedEvaluation.Add(evaluationScore); if (currentBounds.IsLosing()) { m_losingEvaluation.Add(evaluationScore); std::size_t maxWork = 0; for (std::size_t i = 0; i < children.Size(); ++i) { if (childrenData[i].m_work > maxWork) { maxWork = childrenData[i].m_work; bestMove = children.FirstMove(i); } } } else { m_winningEvaluation.Add(evaluationScore); std::size_t minWork = DfpnBounds::INFTY; for (std::size_t i = 0; i < children.Size(); ++i) { if (childrenData[i].m_bounds.IsLosing() && childrenData[i].m_work < minWork) { minWork = childrenData[i].m_work; bestMove = children.FirstMove(i); } } } } // Store search results and notify listeners DfpnData data(currentBounds, children, bestMove, localWork + prevWork, maxProofSet, evaluationScore); TTWrite(*m_state, data); if (data.m_bounds.IsSolved()) NotifyListeners(history, data); return localWork; }