nissy-core

The "engine" of nissy, including the H48 optimal solver.
git clone https://git.tronto.net/nissy-core
Download | Log | Files | Refs | README | LICENSE

commit 8d3fc9f5f9c0593fcae9c451efe6c89c64dcbe84
parent cdf46d85efe7fa54e8c302a255214341c567b4af
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Tue, 29 Apr 2025 10:08:04 +0200

Added stop / pause / resume solve to API

Diffstat:
Mconfigure.sh | 5+++--
Mcpp/examples/solve_h48h3k2.cpp | 2+-
Mcpp/nissy.cpp | 13++++++++++---
Mcpp/nissy.h | 12+++++++++++-
Mpython/nissy_module.c | 6+++---
Mshell/shell.c | 2+-
Msrc/nissy.c | 24++++++++++++++++++------
Msrc/nissy.h | 53++++++++++++++++++++++++++++++++++-------------------
Msrc/solvers/coord/solve.h | 14+++++++++-----
Msrc/solvers/h48/solve.h | 69++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Asrc/utils/sleep.h | 48++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/utils/utils.h | 1+
Mtools/300_solve_small/solve_small.c | 3++-
Mtools/301_solve_file/solve_file.c | 3++-
Mtools/302_solve_multisol/solve_multisol.c | 3++-
Mtools/400_solvetest/solve_test.c | 6++++--
16 files changed, 213 insertions(+), 51 deletions(-)

diff --git a/configure.sh b/configure.sh @@ -85,6 +85,7 @@ validatethreads validatearch STD="-std=c11" +POSIX="-D_POSIX_C_SOURCE=199309L" WFLAGS="-pedantic -Wall -Wextra" WNOFLAGS="-Wno-unused-parameter -Wno-unused-function -Wno-unknown-pragmas" WNOFLAGS="$WNOFLAGS -Wno-unused-command-line-argument" @@ -99,8 +100,8 @@ if [ -n "$SANITIZE" ]; then fi LIBS="-lpthread" -CFLAGS="$STD $LIBS $WFLAGS $WNOFLAGS $AVX -O3 -fPIC -D$ARCH" -DBGFLAGS="$STD $LIBS $WFLAGS $WNOFLAGS $SAN $AVX -g3 -DDEBUG -fPIC -D$ARCH" +CFLAGS="$STD $POSIX $LIBS $WFLAGS $WNOFLAGS $AVX -O3 -fPIC -D$ARCH" +DBGFLAGS="$STD $POSIX $LIBS $WFLAGS $WNOFLAGS $SAN $AVX -g3 -DDEBUG -fPIC -D$ARCH" WASMFLAGS="$STD $LIBS $WFLAGS $WNOFLAGS -O3 -fPIC" MACROS="-DTHREADS=$THREADS" diff --git a/cpp/examples/solve_h48h3k2.cpp b/cpp/examples/solve_h48h3k2.cpp @@ -68,7 +68,7 @@ int main() { // Solve auto solve_result = h48h3k2.solve(c, nissy::nissflag::NORMAL, - 0, maxmoves, 1, 20, 8); + 0, maxmoves, 1, 20, 8, NULL, NULL); // Write the result if (!solve_result.err.ok()) { diff --git a/cpp/nissy.cpp b/cpp/nissy.cpp @@ -20,7 +20,8 @@ extern "C" { long long nissy_checkdata(unsigned long long, const unsigned char *); long long nissy_solve(const char *, const char *, unsigned, unsigned, unsigned, unsigned, unsigned, unsigned, unsigned long long, - const unsigned char *, unsigned, char *, long long *); + const unsigned char *, unsigned, char *, long long *, + int (*)(void *), void *); long long nissy_countmoves(const char *); long long nissy_setlogger(void (*)(const char *, void *), void *); } @@ -47,6 +48,10 @@ namespace nissy { const error error::OPTIONS{-80}; const error error::UNKNOWN{-999}; + const status status::run{0}; + const status status::stop{1}; + const status status::pause{2}; + namespace size { constexpr size_t CUBE = 22; constexpr size_t TRANSFORMATION = 12; @@ -154,7 +159,8 @@ namespace nissy { solver::solve_result solver::solve(const cube& cube, nissflag niss, unsigned minmoves, unsigned maxmoves, unsigned maxsols, unsigned optimal, - unsigned threads) + unsigned threads, int (*poll_status)(void *), + void *poll_status_data) { solver::solve_result result; @@ -171,7 +177,8 @@ namespace nissy { name.c_str(), niss.value, minmoves, maxmoves, maxsols, optimal, threads, data.size(), reinterpret_cast<const unsigned char *>(data.data()), len, - csols.data(), result.stats.data()); + csols.data(), result.stats.data(), poll_status, + poll_status_data); result.err = error{err}; if (err < 0) diff --git a/cpp/nissy.h b/cpp/nissy.h @@ -46,6 +46,15 @@ namespace nissy { static const error UNKNOWN; }; + class status { + public: + int value; + + static const status run; + static const status stop; + static const status pause; + }; + class cube { public: cube(); @@ -86,7 +95,8 @@ namespace nissy { void unload_data(); solve_result solve(const cube&, nissflag, unsigned minmoves, unsigned maxmoves, unsigned maxsols, unsigned optimal, - unsigned threads); + unsigned threads, int (*poll_status)(void *), + void *poll_status_data); static std::variant<solver, error> get(const std::string&); private: diff --git a/python/nissy_module.c b/python/nissy_module.c @@ -300,7 +300,7 @@ solve(PyObject *self, PyObject *args) result = nissy_solve(cube, solver, nissflag, minmoves, maxmoves, maxsolutions, optimal, threads, data->ob_alloc, (unsigned char *)data->ob_bytes, MAX_SOLUTIONS_SIZE, solutions, - stats); + stats, NULL, NULL); Py_END_ALLOW_THREADS if(!check_error(result)) { @@ -350,9 +350,9 @@ static PyMethodDef nissy_methods[] = { { "getcube", getcube, METH_VARARGS, getcube_doc }, { "solverinfo", solverinfo, METH_VARARGS, solverinfo_doc }, { "gendata", gendata, METH_VARARGS, gendata_doc }, - { "checkdata", checkdata, METH_VARARGS, checkdata_doc }, + { "checkdata", _checkdata, METH_VARARGS, checkdata_doc }, { "solve", solve, METH_VARARGS, solve_doc }, - { "countmoves", countmoves, METH_VARARGS, countmoves_doc }, + { "countmoves", _countmoves, METH_VARARGS, countmoves_doc }, { NULL, NULL, 0, NULL } }; diff --git a/shell/shell.c b/shell/shell.c @@ -435,7 +435,7 @@ solve_exec(args_t *args) ret = nissy_solve( args->cube, args->str_solver, nissflag, args->minmoves, args->maxmoves, args->maxsolutions, args->optimal, args->threads, - size, buf, SOLUTIONS_BUFFER_SIZE, solutions, stats); + size, buf, SOLUTIONS_BUFFER_SIZE, solutions, stats, NULL, NULL); free(buf); diff --git a/src/nissy.c b/src/nissy.c @@ -460,7 +460,9 @@ nissy_solve( const unsigned char data[data_size], unsigned sols_size, char sols[sols_size], - long long stats[static NISSY_SIZE_SOLVE_STATS] + long long stats[static NISSY_SIZE_SOLVE_STATS], + int (*poll_status)(void *), + void *poll_status_data ) { oriented_cube_t oc; @@ -475,14 +477,23 @@ nissy_solve( oc = readcube(cube); -/* TODO: solve should handle oriented cubes */ - if (!isconsistent(oc)) { LOG("[solve] Error: cube is invalid\n"); return NISSY_ERROR_INVALID_CUBE; } -/* TODO: checks for minmoves, maxmoves, nissflag */ + if (maxmoves > 20) { + LOG("[solve] 'maxmoves' larger than 20 not supported yet, " + "setting it to 20\n"); + maxmoves = 20; + } + + if (minmoves > maxmoves) { + LOG("[solve] value provided for 'minmoves' (%u) is larger " + "than that provided for 'maxmoves' (%u), setting " + "'minmoves' to %u\n", minmoves, maxmoves, maxmoves); + minmoves = maxmoves; + } if (maxsols == 0) { LOG("[solve] 'maxsols' is 0, returning no solution\n"); @@ -505,11 +516,12 @@ nissy_solve( if (parse_ret != NISSY_OK) return parse_ret; return solve_h48(oc, minmoves, maxmoves, maxsols, - optimal, t, data_size, data, sols_size, sols, stats); + optimal, t, data_size, data, sols_size, sols, stats, + poll_status, poll_status_data); } else if (!strncmp(solver, "coord_", 6)) { return solve_coord_dispatch(oc, solver + 6, nissflag, minmoves, maxmoves, maxsols, optimal, t, data_size, data, - sols_size, sols); + sols_size, sols, poll_status, poll_status_data); } else { LOG("[solve] Error: unknown solver '%s'\n", solver); return NISSY_ERROR_INVALID_SOLVER; diff --git a/src/nissy.h b/src/nissy.h @@ -37,6 +37,11 @@ for example 'rotation UF' or 'mirrored BL'. #define NISSY_NISSFLAG_ALL \ (NISSY_NISSFLAG_NORMAL | NISSY_NISSFLAG_INVERSE | NISSY_NISSFLAG_MIXED) +/* Status for stopping / pausing / resuming a solver */ +#define NISSY_STATUS_RUN 0 +#define NISSY_STATUS_STOP 1 +#define NISSY_STATUS_PAUSE 2 + /* The solved cube */ #define NISSY_SOLVED_CUBE "ABCDEFGH=ABCDEFGHIJKL=A" @@ -296,24 +301,32 @@ nissy_checkdata( Solve the given cube using the given solver and options. Parameters: - cube - The cube to solver. - solver - The name of the solver. - nissflag - The flags for NISS (linear, inverse, mixed, or combinations). - minmoves - The minimum number of moves for a solution. - maxmoves - The maximum number of moves for a solution. - maxsols - The maximum number of solutions. - optimal - The maximum number of moves above the optimal solution length. - threads - The number of threads to use. Must be less than or equalt to - the value of the compile-time constant THREADS. If set to 0, - the default value THREADS will be used. - data_size - The size of the data buffer. - data - The data for the solver. Can be computed with gendata. - This buffer must have 8-byte alignment. - sols_size - The size of the solutions buffer. - sols - The return parameter for the solutions. The solutions are - separated by a '\n' (newline) and a '\0' (NULL character) - terminates the list. - stats - An array to store some statistics about the solve. + cube - The cube to solver. + solver - The name of the solver. + nissflag - The flags for NISS (linear, inverse, mixed, or + combinations; see the constants at the top of this file). + minmoves - The minimum number of moves for a solution. + maxmoves - The maximum number of moves for a solution. + maxsols - The maximum number of solutions. + optimal - The maximum number of moves above the optimal solution. + threads - The number of threads to use. Must be less than or equal + to the value of the compile-time constant THREADS. If set + to 0, the default value THREADS will be used. + data_size - The size of the data buffer. + data - The data for the solver. Can be computed with gendata. + This buffer must have 8-byte alignment. + sols_size - The size of the solutions buffer. + sols - The return parameter for the solutions. The solutions are + separated by a '\n' (newline) and a '\0' (NULL character) + terminates the list. + stats - An array to store some statistics about the solve. + poll_status - A callback function that should return the current + requested status for the solver (e.g. run, stop, pause, + resume; see the constants at the top of this file). The + way this status is polled and honored is solver-specific. + If this parameter is NULL, the status will always assumed + to be "run". + poll_status_data - Auxiliary data for the poll_status callback function. Return values: NISSY_OK - Cube solved succesfully. @@ -340,7 +353,9 @@ nissy_solve( const unsigned char data[data_size], unsigned sols_size, char sols[sols_size], - long long stats[static NISSY_SIZE_SOLVE_STATS] + long long stats[static NISSY_SIZE_SOLVE_STATS], + int (*poll_status)(void *), + void *poll_status_data ); /* diff --git a/src/solvers/coord/solve.h b/src/solvers/coord/solve.h @@ -14,10 +14,10 @@ typedef struct { STATIC int64_t solve_coord(oriented_cube_t, coord_t [static 1], uint8_t, uint8_t, uint8_t, uint8_t, uint64_t, uint8_t, uint8_t, uint64_t, - const unsigned char *, size_t n, char [n]); + const unsigned char *, size_t n, char [n], int (*)(void *), void *); STATIC int64_t solve_coord_dispatch(oriented_cube_t, const char *, uint8_t, uint8_t, uint8_t, uint64_t, uint8_t, uint8_t, uint64_t, - const unsigned char *, size_t n, char [n]); + const unsigned char *, size_t n, char [n], int (*)(void *), void *); STATIC bool coord_solution_admissible(const dfsarg_solve_coord_t [static 1]); STATIC bool solve_coord_dfs_stop(const dfsarg_solve_coord_t [static 1]); STATIC bool coord_continue_onnormal(const dfsarg_solve_coord_t [static 1]); @@ -211,7 +211,9 @@ solve_coord_dispatch( uint64_t data_size, const unsigned char *data, size_t solutions_size, - char sols[solutions_size] + char sols[solutions_size], + int (*poll_status)(void *), + void *poll_status_data ) { coord_t *coord; @@ -233,7 +235,7 @@ solve_coord_dispatch( return solve_coord(oc, coord, axis, nissflag, minmoves, maxmoves, maxsolutions, optimal, threads, data_size, data, - solutions_size, sols); + solutions_size, sols, poll_status, poll_status_data); } STATIC int64_t @@ -250,7 +252,9 @@ solve_coord( uint64_t data_size, const unsigned char *data, size_t solutions_size, - char sols[solutions_size] + char sols[solutions_size], + int (*poll_status)(void *), + void *poll_status_data ) { int8_t d; diff --git a/src/solvers/h48/solve.h b/src/solvers/h48/solve.h @@ -35,6 +35,10 @@ typedef struct { solve_h48_task_t *tasks; int thread_id; pthread_mutex_t *solutions_mutex; + int (*poll_status)(void *); + void *poll_status_data; + _Atomic bool cancelled; + _Atomic bool cantsleep; } dfsarg_solve_h48_t; typedef struct { @@ -50,11 +54,12 @@ STATIC_INLINE bool solve_h48_stop(dfsarg_solve_h48_t [static 1]); STATIC int64_t solve_h48_maketasks( dfsarg_solve_h48_t [static 1], dfsarg_solve_h48_maketasks_t [static 1], solve_h48_task_t [static STARTING_CUBES], int [static 1]); +STATIC bool solve_h48_runthread_continue(dfsarg_solve_h48_t *); STATIC void *solve_h48_runthread(void *); STATIC int64_t solve_h48_dfs(dfsarg_solve_h48_t [static 1]); STATIC int64_t solve_h48(oriented_cube_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint64_t, const unsigned char *, size_t n, char [n], - long long [static NISSY_SIZE_SOLVE_STATS]); + long long [static NISSY_SIZE_SOLVE_STATS], int (*)(void *), void *); STATIC_INLINE bool solve_h48_stop(dfsarg_solve_h48_t arg[static 1]) @@ -232,16 +237,42 @@ solve_h48_dfs(dfsarg_solve_h48_t arg[static 1]) return ret; } +STATIC bool +solve_h48_runthread_continue(dfsarg_solve_h48_t *arg) +{ + int status; + + for (status = NISSY_STATUS_PAUSE; status == NISSY_STATUS_PAUSE; ) { + status = arg->poll_status == NULL ? NISSY_STATUS_RUN : + arg->poll_status(arg->poll_status_data); + msleep(500); + } + + return status == NISSY_STATUS_RUN; +} + STATIC void * solve_h48_runthread(void *arg) { - int i, j; + int i, j, status; solve_h48_task_t task; dfsarg_solve_h48_t *dfsarg; dfsarg = (dfsarg_solve_h48_t *)arg; for (i = dfsarg->thread_id; i < dfsarg->ntasks; i += dfsarg->threads) { + status = dfsarg->poll_status == NULL ? NISSY_STATUS_RUN : + dfsarg->poll_status(dfsarg->poll_status_data); + switch (status) { + case NISSY_STATUS_STOP: + goto solve_h48_runthread_cancel; + case NISSY_STATUS_PAUSE: + if (!NISSY_CANSLEEP) + goto solve_h48_runthread_cantsleep; + if (!solve_h48_runthread_continue(dfsarg)) + goto solve_h48_runthread_cancel; + } + task = dfsarg->tasks[i]; solution_moves_reset(dfsarg->solution_moves); @@ -265,6 +296,12 @@ solve_h48_runthread(void *arg) } return NULL; + +solve_h48_runthread_cantsleep: + dfsarg->cantsleep = true; +solve_h48_runthread_cancel: + dfsarg->cancelled = true; + return NULL; } STATIC int64_t @@ -351,9 +388,12 @@ solve_h48( const unsigned char *data, size_t solutions_size, char solutions[solutions_size], - long long stats[static NISSY_SIZE_SOLVE_STATS] + long long stats[static NISSY_SIZE_SOLVE_STATS], + int (*poll_status)(void *), + void *poll_status_data ) { + bool anycancelled, anycantsleep; int i, ntasks, eoesep_table_index; int8_t d; dfsarg_solve_h48_t arg[THREADS]; @@ -434,6 +474,10 @@ solve_h48( .threads = threads, .thread_id = i, .solutions_mutex = &solutions_mutex, + .poll_status = poll_status, + .poll_status_data = poll_status_data, + .cancelled = false, + .cantsleep = false, }; } @@ -460,9 +504,10 @@ solve_h48( LOG("[H48 solve] Prepared %d tasks\n", ntasks); + anycancelled = anycantsleep = false; for ( d = MAX(minmoves, STARTING_MOVES + 1); - !solutions_done(&sollist, &settings, d); + !(solutions_done(&sollist, &settings, d) || anycancelled); d++ ) { if (d >= 15) @@ -474,8 +519,22 @@ solve_h48( pthread_create( &thread[i], NULL, solve_h48_runthread, &arg[i]); } - for (i = 0; i < threads; i++) + for (i = 0; i < threads; i++) { pthread_join(thread[i], NULL); + anycancelled = anycancelled || arg[i].cancelled; + anycantsleep = anycantsleep || arg[i].cantsleep; + } + } + + if (anycantsleep) { + LOG("[H48 solve] Received pause request, but this feature is " + "not available on this system. " + "Taking it as a stop request.\n"); + } + + if (anycancelled) { + LOG("[H48 solve] Received stop request, ending solution " + "search early.\n"); } solve_h48_done: diff --git a/src/utils/sleep.h b/src/utils/sleep.h @@ -0,0 +1,48 @@ +STATIC void msleep(int); + +#if defined(WIN32) + +#define NISSY_CANSLEEP true + +STATIC void +msleep(int milliseconds) +{ + Sleep(milliseconds); +} + +#elif defined(__wasm__) + +#include <emscripten.h> + +#define NISSY_CANSLEEP true + +STATIC void +msleep(int milliseconds) +{ + emscripten_sleep(milliseconds); +} + +#elif defined(__unix__) + +#include <time.h> + +#define NISSY_CANSLEEP true + +STATIC void +msleep(int milliseconds) +{ + struct timespec t; + t.tv_sec = milliseconds / 1000; + t.tv_nsec = (milliseconds % 1000) * 1000000; + nanosleep(&t, NULL); +} + +#else + +#define NISSY_CANSLEEP false +STATIC void +msleep(int milliseconds) +{ +} + +#endif diff --git a/src/utils/utils.h b/src/utils/utils.h @@ -1,3 +1,4 @@ #include "dbg_log.h" #include "constants.h" #include "math.h" +#include "sleep.h" diff --git a/tools/300_solve_small/solve_small.c b/tools/300_solve_small/solve_small.c @@ -37,7 +37,8 @@ void run(void) { continue; } n = nissy_solve(cube, solver, NISSY_NISSFLAG_NORMAL, - 0, 20, 1, -1, 0, size, buf, SOL_BUFFER_LEN, sol, stats); + 0, 20, 1, -1, 0, size, buf, SOL_BUFFER_LEN, sol, stats, + NULL, NULL); if (n == 0) printf("No solution found\n"); else diff --git a/tools/301_solve_file/solve_file.c b/tools/301_solve_file/solve_file.c @@ -24,7 +24,8 @@ void run(void) { continue; } nsols = nissy_solve(cube, solver, NISSY_NISSFLAG_NORMAL, - 0, 20, 1, -1, 0, size, buf, SOL_BUFFER_LEN, sol, stats); + 0, 20, 1, -1, 0, size, buf, SOL_BUFFER_LEN, sol, stats, + NULL, NULL); if (nsols == 0) printf("No solution found\n"); else diff --git a/tools/302_solve_multisol/solve_multisol.c b/tools/302_solve_multisol/solve_multisol.c @@ -30,7 +30,8 @@ void run(void) { continue; } n = nissy_solve(cube, solver, NISSY_NISSFLAG_NORMAL, - 0, 20, nsol, -1, 0, size, buf, SOL_BUFFER_LEN, sol, stats); + 0, 20, nsol, -1, 0, size, buf, SOL_BUFFER_LEN, sol, stats, + NULL, NULL); if (n == 0) printf("No solution found\n"); else diff --git a/tools/400_solvetest/solve_test.c b/tools/400_solvetest/solve_test.c @@ -57,7 +57,8 @@ void run(void) { continue; } n = nissy_solve(cube, solver, NISSY_NISSFLAG_NORMAL, - 0, 20, 1, -1, 0, size, buf, SOL_BUFFER_LEN, sol, stats); + 0, 20, 1, -1, 0, size, buf, SOL_BUFFER_LEN, sol, stats, + NULL, NULL); if (n == 0) { printf("Error: no solution\n"); return; @@ -78,7 +79,8 @@ void run(void) { continue; } n = nissy_solve(cube, solver, NISSY_NISSFLAG_NORMAL, - 0, 20, 100, 0, 0, size, buf, SOL_BUFFER_LEN, sol, stats); + 0, 20, 100, 0, 0, size, buf, SOL_BUFFER_LEN, sol, stats, + NULL, NULL); if (check_all(sol, s[i].solutions)) { printf("All solutions are correct\n"); } else {