Skip to content

Commit dd2ade5

Browse files
authored
Merge pull request #4 from olarozenfeld/or-fork
Solve speedup ~7.5 times using key_variables
2 parents 363c10d + 763b007 commit dd2ade5

File tree

4 files changed

+13
-124
lines changed

4 files changed

+13
-124
lines changed

WORKSPACE

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,6 @@ cc_library(
106106

107107
git_repository(
108108
name = "com_google_ortools",
109-
#branch = "master",
110-
commit = "525162feaadaeef640783b2eaea38cf4b623877f",
111-
shallow_since = "1647023481 +0100",
112-
#tag = "v9.3",
113-
remote = "https://github.com/google/or-tools.git",
109+
branch = "key-vars", # My patch to OR-Tools for the key_variables feature.
110+
remote = "https://github.com/olarozenfeld/or-tools.git",
114111
)

src/game_sat_solver.cc

Lines changed: 11 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,18 +1387,6 @@ void GameSatSolver::FillWorldFromSolverResponse(
13871387
}
13881388

13891389
SolverResponse GameSatSolver::Solve(const SolverRequest& request) {
1390-
switch (request.strategy()) {
1391-
case ITERATE_ROLE_ASSIGNMENTS:
1392-
return SolveIteration(request);
1393-
case EXPLORE_ALL_SOLUTIONS:
1394-
return SolveFull(request);
1395-
default:
1396-
return (g_.NumMinions() > 1 && g_.GetPerspective() != STORYTELLER ?
1397-
SolveIteration(request) : SolveFull(request));
1398-
}
1399-
}
1400-
1401-
SolverResponse GameSatSolver::SolveFull(const SolverRequest& request) {
14021390
SolverResponse result;
14031391
// Making a copy to add assumptions.
14041392
const auto assumptions = CollectAssumptionLiterals(request.assumptions());
@@ -1413,13 +1401,19 @@ SolverResponse GameSatSolver::SolveFull(const SolverRequest& request) {
14131401
WriteProtoToFile(cp_model.Build(), tmp_dir / "model.pbtxt");
14141402
}
14151403
CpSolverResponse response;
1416-
unordered_map<string, SolverResponse::World*> worlds_per_key;
14171404
map<string, int> num_worlds_per_demon;
14181405
int solutions = 0;
14191406
operations_research::sat::Model model;
14201407
SatParameters parameters;
14211408
parameters.set_enumerate_all_solutions(!request.stop_after_first_solution());
14221409
parameters.set_symmetry_level(0); // Empirically, this is faster.
1410+
// We only care about current (and starting?) role assignments, so add them
1411+
// as key variables:
1412+
for (int i = 0; i < g_.NumPlayers(); ++i) {
1413+
for (Role role : AllRoles(script_)) {
1414+
parameters.add_key_variables(RoleVar(i, role, g_.CurrentTime()).index());
1415+
}
1416+
}
14231417
model.Add(NewSatParameters(parameters));
14241418
auto log_progress = [&](bool force) {
14251419
string progress = absl::StrFormat("Found %d solutions ", solutions);
@@ -1440,14 +1434,10 @@ SolverResponse GameSatSolver::SolveFull(const SolverRequest& request) {
14401434
SolverResponse::World cur_world;
14411435
FillWorldFromSolverResponse(r, &cur_world);
14421436
const int demon = SolutionAliveDemon(r);
1443-
const string key = cur_world.DebugString();
1444-
if (!worlds_per_key.contains(key)) {
1445-
worlds_per_key[key] = result.add_worlds();
1446-
*(worlds_per_key[key]) = cur_world;
1447-
const string demon_name =
1448-
demon == kNoPlayer ? "<Dead player>" : g_.PlayerName(demon);
1449-
num_worlds_per_demon[demon_name]++;
1450-
}
1437+
*(result.add_worlds()) = cur_world;
1438+
const string demon_name =
1439+
demon == kNoPlayer ? "<Dead player>" : g_.PlayerName(demon);
1440+
num_worlds_per_demon[demon_name]++;
14511441
log_progress(false);
14521442
if (debug_mode) {
14531443
const path sat_filename = absl::StrFormat("sat_solution_%d", solutions);
@@ -1466,79 +1456,6 @@ SolverResponse GameSatSolver::SolveFull(const SolverRequest& request) {
14661456
return result;
14671457
}
14681458

1469-
SolverResponse GameSatSolver::SolveIteration(const SolverRequest& request) {
1470-
SolverResponse result;
1471-
// Making a copy to add assumptions repeatedly.
1472-
const auto assumptions = CollectAssumptionLiterals(request.assumptions());
1473-
CpModelBuilder model(model_.Model());
1474-
model.AddBoolAnd(assumptions);
1475-
path tmp_dir = "./tmp";
1476-
path solution_dir = tmp_dir / "solutions";
1477-
const bool debug_mode = request.debug_mode();
1478-
if (debug_mode) {
1479-
// Create the ./tmp/solutions directory, if not present.
1480-
create_directories(solution_dir);
1481-
WriteProtoToFile(model.Build(), tmp_dir / "model.pbtxt");
1482-
}
1483-
CpSolverResponse response;
1484-
map<string, int> num_worlds_per_demon;
1485-
int solutions = 0;
1486-
while (true) {
1487-
response = operations_research::sat::Solve(model.Build());
1488-
CHECK(response.status() != CpSolverStatus::MODEL_INVALID);
1489-
if (response.status() == CpSolverStatus::UNKNOWN) {
1490-
LOG(WARNING) << "Solver was interrupted, returning partial solution";
1491-
break;
1492-
}
1493-
if (response.status() == CpSolverStatus::INFEASIBLE) {
1494-
break;
1495-
}
1496-
++solutions;
1497-
if (debug_mode) {
1498-
const path resp_filename = absl::StrFormat("resp_%d.pbtxt", solutions);
1499-
WriteProtoToFile(response, solution_dir / resp_filename);
1500-
}
1501-
const int demon = SolutionAliveDemon(response);
1502-
SolverResponse::World *world = result.add_worlds();
1503-
FillWorldFromSolverResponse(response, world);
1504-
const string demon_name =
1505-
demon == kNoPlayer ? "<Dead player>" : g_.PlayerName(demon);
1506-
num_worlds_per_demon[demon_name]++;
1507-
vector<BoolVar> current;
1508-
for (int i = 0; i < g_.NumPlayers(); ++i) {
1509-
for (Role role : AllRoles(script_)) {
1510-
const BoolVar& current_i_role = RoleVar(i, role, g_.CurrentTime());
1511-
if (SolutionBooleanValue(response, current_i_role)) {
1512-
current.push_back(current_i_role);
1513-
}
1514-
}
1515-
}
1516-
string progress = absl::StrFormat("Found %d solutions ", solutions);
1517-
for (const auto& it : num_worlds_per_demon) {
1518-
absl::StrAppend(
1519-
&progress, absl::StrFormat("%s: %d ", it.first, it.second));
1520-
}
1521-
cout << progress << endl;
1522-
if (debug_mode) {
1523-
const path sat_filename = absl::StrFormat("sat_solution_%d", solutions);
1524-
model_.WriteSatSolutionToFile(response, solution_dir / sat_filename);
1525-
const path world_filename = absl::StrFormat("world_%d.pbtxt", solutions);
1526-
WriteProtoToFile(*world, solution_dir / world_filename);
1527-
}
1528-
if (request.stop_after_first_solution()) {
1529-
break;
1530-
}
1531-
// Limit further solutions to different role assignments.
1532-
model.AddBoolOr(Not(current)); // At least one literal is different.
1533-
}
1534-
for (const auto& it : num_worlds_per_demon) {
1535-
auto* ado = result.add_alive_demon_options();
1536-
ado->set_name(it.first);
1537-
ado->set_count(it.second);
1538-
}
1539-
return result;
1540-
}
1541-
15421459
// Solves the game and returns all valid worlds.
15431460
SolverResponse Solve(const GameState& g) {
15441461
return GameSatSolver(g).Solve();

src/game_sat_solver.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,6 @@ class GameSatSolver {
211211
SolverResponse::World* world);
212212
int SolutionAliveDemon(const CpSolverResponse& response);
213213

214-
// Calls sat::Solve in a loop while updating the model.
215-
SolverResponse SolveIteration(const SolverRequest& request);
216-
217-
// Calls sat::Solve once to iterate over all boolean solutions. Can be faster,
218-
// but can also be prohibitively slow at this point.
219-
SolverResponse SolveFull(const SolverRequest& request);
220-
221214
const GameState& g_; // Current game state.
222215
const Script script_; // Part of g_, replicated for convenience.
223216
// Preprocessed game state variables.

src/solver.proto

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,6 @@ package botc;
1717

1818
import "src/game_log.proto";
1919

20-
enum SolverStrategy {
21-
// Will choose based on game size.
22-
SOLVER_STRATEGY_UNSPECIFIED = 0;
23-
24-
// Will iteratively solve from scratch with the previously found role
25-
// assignment disallowed. This is slow, but reliably slow.
26-
ITERATE_ROLE_ASSIGNMENTS = 1;
27-
28-
// Will use the SAT solver to explore all SAT solutions and map them to role
29-
// assignments. This is sometimes faster than the previous option, but can be
30-
// prohibitively slow.
31-
EXPLORE_ALL_SOLUTIONS = 2;
32-
}
33-
3420
message SolverRequest {
3521
message Assumptions {
3622
message PlayerRole {
@@ -76,10 +62,6 @@ message SolverRequest {
7662
// variable assignment files alongside world assignments for each solution
7763
// under the "./tmp/" directory.
7864
bool debug_mode = 3;
79-
80-
// Which solver strategy to use. A default one tries to estimate which will be
81-
// faster for the current game, but it may be wrong.
82-
SolverStrategy strategy = 4;
8365
}
8466

8567
message SolverResponse {

0 commit comments

Comments
 (0)