Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 167 additions & 3 deletions cpp/command/analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ struct AnalyzeRequest {
vector<int> avoidMoveUntilByLocBlack;
vector<int> avoidMoveUntilByLocWhite;

//Root focus moves, their relative weights, and the probability that each root playout is redirected into one of them.
//The set_focus action may change these while the request is open, under openRequestsMutex.
vector<Loc> focusMoves;
vector<double> focusWeights;
double focusProb;

//Starts with STATUS_IN_QUEUE.
//Thread that grabs it from queue it changes it to STATUS_POPPED
//Once search is fully started thread sticks in its own thread index
Expand All @@ -56,6 +62,9 @@ struct AnalyzeRequest {
std::atomic<int> status;
};

//Used when focusMoves is specified without focusProb.
static constexpr double DEFAULT_FOCUS_PROB = 0.5;


int MainCmds::analysis(const vector<string>& args) {
Board::initHash();
Expand Down Expand Up @@ -244,7 +253,11 @@ int MainCmds::analysis(const vector<string>& args) {
"firstReportDuringSearchAfter",
"priority",
"allowMoves",
"avoidMoves"
"avoidMoves",
"focusMoves",
"focusWeights",
"focusProb",
"targetId"
};

ThreadSafeQueue<string*> toWriteQueue;
Expand Down Expand Up @@ -367,14 +380,20 @@ int MainCmds::analysis(const vector<string>& args) {
double searchFactor = 1.0;

//Handle termination between the time we pop and the search starts
std::function<void()> onSearchBegun = [&request,&bot,&threadIdx]() {
std::function<void()> onSearchBegun = [&request,&bot,&threadIdx,&openRequestsMutex]() {
//Try to record that we're handling this request and indicate that the search is started by this thread
int expected2 = AnalyzeRequest::STATUS_POPPED;
//If it was terminated, then stop our search
if(!request->status.compare_exchange_strong(expected2, threadIdx, std::memory_order_acq_rel)) {
testAssert(expected2 == AnalyzeRequest::STATUS_TERMINATED);
bot->stopWithoutWait();
}
//Apply the focus only now that our thread index is recorded, so that a set_focus action either updated
//the fields we read here or saw our thread index and set the focus on the bot itself. See setRequestFocus.
{
std::lock_guard<std::mutex> lock(openRequestsMutex);
bot->setRootFocus(request->focusMoves,request->focusWeights,request->focusProb);
}
};

if(request->reportDuringSearch) {
Expand Down Expand Up @@ -472,6 +491,42 @@ int MainCmds::analysis(const vector<string>& args) {
}
};

//Parse an optional focusWeights field, which must be an array of positive numbers parallel to focusMoves.
auto parseFocusWeights = [&reportErrorForId](const string& id, const json& dict, size_t numFocusMoves, vector<double>& buf) {
const json& weights = dict["focusWeights"];
if(!weights.is_array() || weights.size() != numFocusMoves) {
reportErrorForId(id, "focusWeights", "Must be an array of positive numbers with the same length as focusMoves");
return false;
}
buf.clear();
for(const json& elt : weights) {
double weight = 0.0;
if(elt.is_number())
weight = elt.get<double>();
if(!isfinite(weight) || weight <= 0.0) {
reportErrorForId(id, "focusWeights", "Must be an array of positive numbers with the same length as focusMoves");
return false;
}
buf.push_back(weight);
}
return true;
};

//Change the focus of a request. Must be called with openRequestsMutex held, which guarantees that the request is
//still open and so any thread index in its status still refers to the bot searching it.
//The fields are written before the status is read here, while the analyzing thread stores its thread index into
//the status before it reads the fields under the same mutex. So either the thread sees the new fields when it
//starts, or we see the thread index and set the focus on its bot directly. Setting the focus on a bot whose search
//for this request has just ended is harmless, since the next request resets it.
auto setRequestFocus = [&bots](AnalyzeRequest* request, const vector<Loc>& focusMoves, const vector<double>& focusWeights, double focusProb) {
request->focusMoves = focusMoves;
request->focusWeights = focusWeights;
request->focusProb = focusProb;
int status = request->status.load(std::memory_order_acquire);
if(status >= 0)
bots[status]->setRootFocus(focusMoves,focusWeights,focusProb);
};

auto requestLoop = [&]() {
string line;
json input;
Expand Down Expand Up @@ -607,8 +662,95 @@ int MainCmds::analysis(const vector<string>& args) {
}
pushToWrite(new string(input.dump()));
}
else if(action == "set_focus") {
string targetId;
if(input.find("targetId") != input.end() && input["targetId"].is_string()) {
targetId = input["targetId"].get<string>();
}
else {
reportErrorForId(rbase.id, "targetId", "Requests for a set_focus action must have a string \"targetId\" field");
continue;
}

bool hasTurnNumbers = false;
vector<int> turnNumbers;
if(input.find("turnNumbers") != input.end()) {
try {
turnNumbers = input["turnNumbers"].get<vector<int> >();
hasTurnNumbers = true;
}
catch(nlohmann::detail::exception&) {
reportErrorForId(rbase.id, "turnNumbers", "If provided, must be an array of integers indicating turns to change the focus of");
continue;
}
}

vector<string> focusMoveStrs;
if(input.find("focusMoves") != input.end()) {
try {
focusMoveStrs = input["focusMoves"].get<vector<string> >();
}
catch(nlohmann::detail::exception&) {
reportErrorForId(rbase.id, "focusMoves", "Must be an array of GTP board vertices");
continue;
}
}

vector<double> focusWeights(focusMoveStrs.size(), 1.0);
if(input.find("focusWeights") != input.end()) {
if(!parseFocusWeights(rbase.id, input, focusMoveStrs.size(), focusWeights))
continue;
}

double focusProb = DEFAULT_FOCUS_PROB;
if(input.find("focusProb") != input.end()) {
bool valid = input["focusProb"].is_number();
if(valid) {
focusProb = input["focusProb"].get<double>();
valid = isfinite(focusProb) && focusProb >= 0.0 && focusProb <= 1.0;
}
if(!valid) {
reportErrorForId(rbase.id, "focusProb", "Must be a number from 0.0 to 1.0");
continue;
}
}

bool failed = false;
{
std::lock_guard<std::mutex> lock(openRequestsMutex);
std::set<int> turnNumbersSet(turnNumbers.begin(),turnNumbers.end());
//The board size is only known per request, so parse the moves against each matching request's own board,
//and apply the updates only if all of them parse.
vector<std::pair<AnalyzeRequest*,vector<Loc> > > updates;
for(auto it = openRequests.begin(); it != openRequests.end(); ++it) {
AnalyzeRequest* request = it->second;
if(request->id != targetId || (hasTurnNumbers && turnNumbersSet.find(request->turnNumber) == turnNumbersSet.end()))
continue;
vector<Loc> focusMoves;
for(const string& s: focusMoveStrs) {
Loc loc;
if(!Location::tryOfString(s, request->board.x_size, request->board.y_size, loc) || loc == Board::NULL_LOC) {
reportErrorForId(rbase.id, "focusMoves", "Could not parse board location: " + s);
failed = true;
break;
}
focusMoves.push_back(loc);
}
if(failed)
break;
updates.push_back(std::make_pair(request,focusMoves));
}
if(!failed) {
for(size_t i = 0; i<updates.size(); i++)
setRequestFocus(updates[i].first, updates[i].second, focusWeights, focusProb);
}
}
if(failed)
continue;
pushToWrite(new string(input.dump()));
}
else {
reportError("'action' field must be 'query_version' or 'query_models' or 'clear_cache' or 'terminate' or 'terminate_all'");
reportError("'action' field must be 'query_version' or 'query_models' or 'clear_cache' or 'terminate' or 'terminate_all' or 'set_focus'");
}

continue;
Expand All @@ -631,6 +773,9 @@ int MainCmds::analysis(const vector<string>& args) {
rbase.priority = 0;
rbase.avoidMoveUntilByLocBlack.clear();
rbase.avoidMoveUntilByLocWhite.clear();
rbase.focusMoves.clear();
rbase.focusWeights.clear();
rbase.focusProb = DEFAULT_FOCUS_PROB;

auto parseInteger = [&rbase,&reportErrorForId](const json& dict, const char* field, int64_t& buf, int64_t min, int64_t max, const char* errorMessage) {
try {
Expand Down Expand Up @@ -1136,6 +1281,22 @@ int MainCmds::analysis(const vector<string>& args) {
continue;
}

if(input.find("focusMoves") != input.end()) {
bool suc = parseBoardLocs(input, "focusMoves", rbase.focusMoves, true);
if(!suc)
continue;
}
rbase.focusWeights.assign(rbase.focusMoves.size(), 1.0);
if(input.find("focusWeights") != input.end()) {
bool suc = parseFocusWeights(rbase.id, input, rbase.focusMoves.size(), rbase.focusWeights);
if(!suc)
continue;
}
if(input.find("focusProb") != input.end()) {
bool suc = parseDouble(input, "focusProb", rbase.focusProb, 0.0, 1.0, "Must be a number from 0.0 to 1.0");
if(!suc)
continue;
}

Board board(boardXSize,boardYSize);
for(int i = 0; i<placements.size(); i++) {
Expand Down Expand Up @@ -1207,6 +1368,9 @@ int MainCmds::analysis(const vector<string>& args) {
newRequest->priority = priority;
newRequest->avoidMoveUntilByLocBlack = rbase.avoidMoveUntilByLocBlack;
newRequest->avoidMoveUntilByLocWhite = rbase.avoidMoveUntilByLocWhite;
newRequest->focusMoves = rbase.focusMoves;
newRequest->focusWeights = rbase.focusWeights;
newRequest->focusProb = rbase.focusProb;
newRequest->status.store(AnalyzeRequest::STATUS_IN_QUEUE,std::memory_order_release);
newRequests.push_back(newRequest);
}
Expand Down
55 changes: 55 additions & 0 deletions cpp/command/gtp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,9 @@ struct GTPEngine {
double secondsPerReport = TimeControls::UNLIMITED_TIME_DEFAULT;
vector<int> avoidMoveUntilByLocBlack;
vector<int> avoidMoveUntilByLocWhite;
vector<Loc> focusMoves;
vector<double> focusWeights;
double focusProb = 0.0;
};

void filterZeroVisitMoves(const AnalyzeArgs& args, vector<AnalysisData> buf) {
Expand Down Expand Up @@ -1152,6 +1155,7 @@ struct GTPEngine {
lastSearchFactor = searchFactor;

bot->setAvoidMoveUntilByLoc(args.avoidMoveUntilByLocBlack,args.avoidMoveUntilByLocWhite);
bot->setRootFocus(args.focusMoves,args.focusWeights,args.focusProb);

//So that we can tell by the end of the search whether we still care for the result.
int expectedSearchId = (genmoveExpectedId.load() + 1) & 0x3FFFFFFF;
Expand Down Expand Up @@ -1424,6 +1428,7 @@ struct GTPEngine {

std::function<void(const Search* search)> callback = getAnalyzeCallback(pla,args);
bot->setAvoidMoveUntilByLoc(args.avoidMoveUntilByLocBlack,args.avoidMoveUntilByLocWhite);
bot->setRootFocus(args.focusMoves,args.focusWeights,args.focusProb);
if(args.showOwnership || args.showOwnershipStdev || args.showMovesOwnership || args.showMovesOwnershipStdev)
bot->setAlwaysIncludeOwnerMap(true);
else
Expand Down Expand Up @@ -1765,6 +1770,10 @@ static GTPEngine::AnalyzeArgs parseAnalyzeCommand(
bool gotAllowMovesBlack = false;
bool gotAvoidMovesWhite = false;
bool gotAllowMovesWhite = false;
vector<Loc> focusMoves;
vector<double> focusWeights;
double focusProb = 0.0;
bool gotFocus = false;

parseFailed = false;

Expand All @@ -1774,6 +1783,7 @@ static GTPEngine::AnalyzeArgs parseAnalyzeCommand(

//interval <float interval in centiseconds>
//avoid <player> <comma-separated moves> <until movenum>
//focus <comma-separated moves, each optionally suffixed with :weight> <probability>
//minmoves <int min number of moves to show>
//maxmoves <int max number of moves to show>
//ownership <bool whether to show ownership or not>
Expand Down Expand Up @@ -1870,6 +1880,48 @@ static GTPEngine::AnalyzeArgs parseAnalyzeCommand(

continue;
}
else if(key == "focus") {
//Can only be specified once. Parse one more argument.
if(gotFocus || pieces.size() < numArgsParsed+1) {
parseFailed = true;
break;
}
gotFocus = true;
const string& probStr = pieces[numArgsParsed];
numArgsParsed += 1;

if(!Global::tryStringToDouble(probStr,focusProb) || isnan(focusProb) || focusProb < 0.0 || focusProb > 1.0) {
parseFailed = true;
break;
}
vector<string> locPieces = Global::split(value,',');
for(size_t i = 0; i<locPieces.size(); i++) {
string s = Global::trim(locPieces[i]);
if(s.size() <= 0)
continue;
//Each move may carry a weight after a colon, such as C3:2. Missing weights default to 1.
double weight = 1.0;
size_t colonPos = s.find(':');
if(colonPos != string::npos) {
string weightStr = s.substr(colonPos+1);
s = s.substr(0,colonPos);
if(!Global::tryStringToDouble(weightStr,weight) || !isfinite(weight) || weight <= 0.0) {
parseFailed = true;
break;
}
}
Loc loc;
if(!tryParseLoc(s,engine->bot->getRootBoard(),loc)) {
parseFailed = true;
break;
}
focusMoves.push_back(loc);
focusWeights.push_back(weight);
}
if(parseFailed)
break;
continue;
}
else if(key == "minmoves" && Global::tryStringToInt(value,minMoves) &&
minMoves >= 0 && minMoves < 1000000000) {
continue;
Expand Down Expand Up @@ -1925,6 +1977,9 @@ static GTPEngine::AnalyzeArgs parseAnalyzeCommand(
args.showNoResultValue = showNoResultValue;
args.avoidMoveUntilByLocBlack = avoidMoveUntilByLocBlack;
args.avoidMoveUntilByLocWhite = avoidMoveUntilByLocWhite;
args.focusMoves = focusMoves;
args.focusWeights = focusWeights;
args.focusProb = focusProb;
return args;
}

Expand Down
3 changes: 3 additions & 0 deletions cpp/search/asyncbot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ void AsyncBot::setAvoidMoveUntilRescaleRoot(bool b) {
stopAndWait();
search->setAvoidMoveUntilRescaleRoot(b);
}
void AsyncBot::setRootFocus(const std::vector<Loc>& moves, const std::vector<double>& weights, double prob) {
search->setRootFocus(moves,weights,prob);
}
void AsyncBot::setRootHintLoc(Loc loc) {
stopAndWait();
search->setRootHintLoc(loc);
Expand Down
3 changes: 3 additions & 0 deletions cpp/search/asyncbot.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class AsyncBot {
void setRootHintLoc(Loc loc);
void setAvoidMoveUntilByLoc(const std::vector<int>& bVec, const std::vector<int>& wVec);
void setAvoidMoveUntilRescaleRoot(bool b);
//Exception to the above: does not stop the search. Safe to call at any time, including during a search,
//and takes effect for its subsequent playouts.
void setRootFocus(const std::vector<Loc>& moves, const std::vector<double>& weights, double prob);
void setAlwaysIncludeOwnerMap(bool b);
void setParams(const SearchParams& params);
void setParamsNoClearing(const SearchParams& params);
Expand Down
Loading
Loading