diff --git a/README.md b/README.md index fbb10d6..4814841 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ The CMD-program provides the following options: * `-c` (default OFF): Compress solutions to AXHT. This is especially useful when solving in AXQT as properly merging move sequences like `D (U D)` is not entirely trivial without having all the proper move definitions at the ready. +* `-i` (default OFF): Immediately display new solutions as they are found, instead of displaying them after the timeout. + If the `-m` parameter is provided, the search stops after the timeout, otherwise the search runs + forever and can be aborted by pressing CTRL-C. + +* `-l` (default -1): Maximum solutions length. The search will stop once a solution of at most this length is found. With `-1` the solver will simply search for the full time-limit and eventually return the best solution found. * `-l` (default -1): Maximum solution length. The search will stop once a solution of at most this length is found. With `-1` the solver will simply search for the full time-limit and eventually return the best solution found. * `-m` (default 10): Time-limit in milliseconds. diff --git a/makefile b/makefile index d003b13..59a5c83 100644 --- a/makefile +++ b/makefile @@ -1,7 +1,8 @@ C=gcc CXX=g++ RM=rm -f -CPPFLAGS=-std=c++11 -lpthread -O3 +CPPFLAGS=-std=c++11 -lpthread -O3 -DQT -DAX +# CPPFLAGS=-std=c++11 -lpthread -O3 LDFLAGS= LDLIBS=-lpthread diff --git a/src/main.cpp b/src/main.cpp index 881df75..836d441 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "cubie.h" #include "coord.h" @@ -17,7 +18,7 @@ const std::string BENCH_FILE = "bench.cubes"; void usage() { std::cout << "Usage: ./twophase " - << "[-c] [-l MAX_LEN = 1] [-m MILLIS = 10] [-n N_SOLS = 1] [-s N_SPLITS = 1] [-t N_THREADS = 1] [-w N_WARMUPS = 0]" + << "[-c] [-i] [-l MAX_LEN = 1] [-m MILLIS = 10] [-n N_SOLS = 1] [-s N_SPLITS = 1] [-t N_THREADS = 1] [-w N_WARMUPS = 0]" << std::endl; exit(1); } @@ -77,6 +78,20 @@ double mean(const std::vector>& sols, int (*len)(const std::vec return total / sols.size(); } +solve::Engine *ctrl_c_handler_engine = NULL; +bool ctrl_c_handler_solving_now = false; + +void ctrl_c_handler(int s) +{ + if (ctrl_c_handler_engine == NULL) + exit(0); + + if (!ctrl_c_handler_solving_now) + exit(0); + + ctrl_c_handler_engine->abort(); +} + int main(int argc, char *argv[]) { int n_threads = 1; int tlim = 10; @@ -85,19 +100,26 @@ int main(int argc, char *argv[]) { int n_splits = 1; bool compress = false; int n_warmups = 0; - + + bool display_solutions_immediately = false; // -i flag + bool infinite_search_duration = true; // only enabled if display_solutions_immediately==true + // and -m is not given try { int opt; - while ((opt = getopt(argc, argv, "cl:m:n:s:t:w:")) != -1) { + while ((opt = getopt(argc, argv, "cil:m:n:s:t:w:")) != -1) { switch (opt) { case 'c': compress = true; break; + case 'i': + display_solutions_immediately = true; + break; case 'l': max_len = std::stoi(optarg); break; case 'm': tlim = std::stoi(optarg); + infinite_search_duration = false; break; case 'n': if ((n_sols = std::stoi(optarg)) <= 0) { @@ -130,12 +152,27 @@ int main(int argc, char *argv[]) { } catch (...) { // catch any integer conversion errors usage(); } + infinite_search_duration &= display_solutions_immediately; + if (infinite_search_duration) + tlim = -1; std::cout << "This is rob-twophase v2.0; copyright Elias Frantar 2020." << std::endl << std::endl; init(); - solve::Engine solver(n_threads, tlim, n_sols, max_len, n_splits); + solve::Engine solver(n_threads, tlim, n_sols, max_len, n_splits, display_solutions_immediately, compress); warmup(solver, n_warmups); + // register signal handler for aborting search in immediate mode + if (display_solutions_immediately) + { + ctrl_c_handler_engine = &solver; + + struct sigaction sigIntHandler; + sigIntHandler.sa_handler = ctrl_c_handler; + sigemptyset(&sigIntHandler.sa_mask); + sigIntHandler.sa_flags = 0; + sigaction(SIGINT, &sigIntHandler, NULL); + } + std::cout << "Enter >>solve FACECUBE<< to solve, >>scramble<< to scramble or >>bench<< to benchmark." << std::endl << std::endl; std::string mode; @@ -242,21 +279,24 @@ int main(int argc, char *argv[]) { } auto tick = std::chrono::high_resolution_clock::now(); + ctrl_c_handler_solving_now = true; solver.solve(c, sols); + ctrl_c_handler_solving_now = false; std::cout << std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - tick ).count() / 1000. << "ms" << std::endl; - for (std::vector& sol : sols) { - int len = sol.size(); // always print uncompressed length - if (compress) - std::cout << move::compress(sol) << " "; - else { - for (int m : sol) - std::cout << move::names[m] << " "; + if (!display_solutions_immediately) + for (std::vector& sol : sols) { + int len = sol.size(); // always print uncompressed length + if (compress) + std::cout << move::compress(sol) << " "; + else { + for (int m : sol) + std::cout << move::names[m] << " "; + } + std::cout << "(" << len << ")" << std::endl; } - std::cout << "(" << len << ")" << std::endl; - } } } solver.finish(); // clean exit diff --git a/src/solve.cpp b/src/solve.cpp index d7be9e5..3899f63 100644 --- a/src/solve.cpp +++ b/src/solve.cpp @@ -1,6 +1,7 @@ #include "solve.h" #include +#include #include #include #include "prun.h" @@ -177,8 +178,13 @@ namespace solve { Engine::Engine( int n_threads, int tlim, - int n_sols, int max_len, int n_splits - ) : n_threads(n_threads), tlim(tlim), n_sols(n_sols), max_len(max_len), n_splits(n_splits) { + int n_sols, int max_len, int n_splits, + bool display_solutions_immediately, + bool compress + ) : n_threads(n_threads), tlim(tlim), + n_sols(n_sols), max_len(max_len), n_splits(n_splits), + display_solutions_immediately(display_solutions_immediately), + compress(compress) { int tmp = (move::COUNT1 + n_splits - 1) / n_splits; // ceil to make sure that we always include all moves for (int i = 0; i < n_splits; i++) masks[i] = (move::mask(1) << tmp) - 1 << tmp * i; @@ -228,6 +234,8 @@ namespace solve { cubie::cube invc; cubie::inv(c, invc); + min_reported_length = -1; + for (int dir = 0; dir < N_DIRS; dir++) { const cubie::cube& c1 = (dir & 1) ? invc : c; // reference is enough, we do not need to copy int rot = sym::ROT * (dir / 2); @@ -250,7 +258,10 @@ namespace solve { { // timeout std::unique_lock lock(tout_mtx); - tout_cvar.wait_for(lock, std::chrono::milliseconds(tlim), [&]{ return done; }); + if (tlim == -1) + tout_cvar.wait(lock, [&] { return done; }); + else + tout_cvar.wait_for(lock, std::chrono::milliseconds(tlim), [&] { return done; }); if (!done) done = true; // if we get here, this was a timeout } @@ -281,10 +292,16 @@ namespace solve { if (done) // prevent any type of reporting after the solver has terminated (important for threading) return; + bool print_sol = false; + sols.push(sol); // usually we only get here if we actually have a solution that will be added if (sols.size() > n_sols) + { + print_sol = sols.top() != sol; // only print solution if is was actually added sols.pop(); + } if (sols.size() == n_sols) { + print_sol = true; lenlim = sols.top().first.size(); // only search for strictly shorter solutions if (lenlim <= max_len) { // already found a solution that is short enough @@ -294,6 +311,41 @@ namespace solve { tout_cvar.notify_one(); } } + + if (print_sol + && display_solutions_immediately + && (min_reported_length == -1 || sol.first.size() < min_reported_length) + ) + { + min_reported_length = sol.first.size(); + std::vector res; + res.resize(sol.first.size()); + + int rot = sym::ROT * (sol.second / 2); + for (int j = 0; j < res.size(); j++) // undo rotation + res[j] = sym::conj_move[sol.first[j]][rot]; + + if (sol.second & 1) + { // undo inversion + for (int j = 0; j < res.size(); j++) + res[j] = move::inv[res[j]]; + std::reverse(res.begin(), res.end()); + } + if (compress) + std::cout << move::compress(res) << " "; + else + for (int m : res) + std::cout << move::names[m] << " "; + std::cout << "(" << sol.first.size() << ")" << std::endl; + } + } + + void Engine::abort() + { + std::cout << "Aborting active solve" << std::endl; + done = true; + std::lock_guard lock(tout_mtx); + tout_cvar.notify_one(); } void Engine::finish() { diff --git a/src/solve.h b/src/solve.h index 9d158bc..caad338 100644 --- a/src/solve.h +++ b/src/solve.h @@ -38,6 +38,10 @@ namespace solve { int max_len; // find solutions with at most this length; -1 means simply search for the full `tlimit` int tlim; // search for this amount of milliseconds + bool display_solutions_immediately; // if true, print solutions as the are found, -i flag + bool compress; // as in main.cpp (use for -i flag) + int min_reported_length; // for -i flag, to ensure that only better solutions are output + coordc dirs[N_DIRS]; // search directions move::mask masks[move::COUNT1]; // split masks int depths[N_DIRS]; // current search depths per direction @@ -57,12 +61,15 @@ namespace solve { public: Engine( int n_threads, int tlim, - int n_sols = 1, int max_len = -1, int n_splits = 1 + int n_sols = 1, int max_len = -1, int n_splits = 1, + bool display_solutions_immediately = false, + bool compress = false ); void prepare(); // setup all threads void solve(const cubie::cube& c, std::vector>& res); // actual solve void finish(); // wait for all threads to shutdown (mostly for clean program exit) void report_sol(searchres& sol); // report a solution; never call this from the outside + void abort(); // stop a correctly running solve void thread(); // search thread