h48

A prototype for an optimal Rubik's cube solver, work in progress.
git clone https://git.tronto.net/h48
Download | Log | Files | Refs | README | LICENSE

commit 3c9efb490ccbe1b0d7768d77fcdcac012c00e8c6
parent 05bdcfef13ef2bdd05df6af13a74ec3cd7fcd22f
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Sun, 30 Jun 2024 12:44:47 +0200

Added a solver, but found out h48 table is broken

Diffstat:
M.gitignore | 1+
MMakefile | 6+++++-
MREADME.md | 17+++++++++++++++++
MTODO.txt | 12+++++++++++-
Mshell.c | 97++++++++++++++++++++++++++++++++++++++-----------------------------------------
Msrc/cube_public.h | 11++++++++---
Msrc/solve_generic.h | 3+--
Msrc/solve_h48.h | 174++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mtest/103_gendata_h48/00_h_0.out | 2+-
Mtest/103_gendata_h48/01_h_1.out | 2+-
10 files changed, 265 insertions(+), 60 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -5,6 +5,7 @@ gen debuggen perf.data perf.data.old +run tables/* test/*/runtest test/run diff --git a/Makefile b/Makefile @@ -24,4 +24,8 @@ shell: cube.o mkdir -p tables ${CC} ${CFLAGS} -o run cube.o shell.c -.PHONY: all clean test benchmark shell +debugshell: debugcube.o + mkdir -p tables + ${CC} ${DBGFLAGS} -o run debugcube.o shell.c + +.PHONY: all clean test benchmark shell debugshell diff --git a/README.md b/README.md @@ -27,3 +27,20 @@ $ TEST=coord make test ``` Due to ongoing changes, benchmarks are currently broken. + +## Solving + +Notes for myself while this is work in progress + +``` +$ make shell +$ ./run frommoves -moves (scramble) +``` + +copy the result, then + +``` +$ ./run solve -solver "H48" -options "2;20" -n 1 -M 10 -cube (paste here) +``` + +Options can be changed from `2;20` to `n;20` for larger tables. diff --git a/TODO.txt b/TODO.txt @@ -1,8 +1,16 @@ Solver - - write a solver (how many tricks? some, but not all are needed) + - fix and cleanup current implementation + - fails for UFRUFU when using full table + SOMETHING IS WRONG FOR coord_h48 = 3! (pvalue 15??) + - do not copy dfsarg, change and undo + - implement and use premove (and test) instead of inverting - benchmark for solve table generation, where to keep tables? in benchmark folder or in tables/? + - more tricks for solver, optimize, try larger tables - remove solve_simple and maybe the whole solve_generic + - shell: silently accept other formats too? + - shell: allow generating multiple tables for different options + - gendata: move info at start of tables? Cleanup cube_public and interface - remove options, use only solver name @@ -17,6 +25,8 @@ Goal: find out which k value is best Improvements - check hash of generated data + - use interleaved tables (e.g. big table with k=2 or k=1 and interleaved + small table with k=4 for better backup pruning) ## H48 optimal solver (some has already been implemented) diff --git a/shell.c b/shell.c @@ -1,5 +1,6 @@ #include <inttypes.h> #include <errno.h> +#include <stdarg.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> @@ -28,7 +29,6 @@ typedef struct { int8_t maxmoves; int8_t optimal; int64_t maxsolutions; - int64_t datasize; /* Option for gendata + solve, TODO change? */ } args_t; static void print_cube_result(int64_t, char [static 22]); @@ -39,9 +39,7 @@ static int64_t inverse_exec(args_t *); static int64_t applymoves_exec(args_t *); static int64_t applytrans_exec(args_t *); static int64_t frommoves_exec(args_t *); -static int64_t readcube_exec(args_t *); -static int64_t writecube_exec(args_t *); -static int64_t convertcube_exec(args_t *); +static int64_t convert_exec(args_t *); static int64_t datasize_exec(args_t *); static int64_t gendata_exec(args_t *); static int64_t solve_exec(args_t *); @@ -76,9 +74,7 @@ struct { COMMAND("applymoves", applymoves_exec), COMMAND("applytrans", applytrans_exec), COMMAND("frommoves", frommoves_exec), - COMMAND("readcube", readcube_exec), - COMMAND("writecube", writecube_exec), - COMMAND("convertcube", convertcube_exec), + COMMAND("convert", convert_exec), COMMAND("datasize", datasize_exec), COMMAND("gendata", gendata_exec), COMMAND("solve", solve_exec), @@ -199,43 +195,19 @@ frommoves_exec(args_t *args) char result[22]; int64_t ret; - ret = nissy_frommoves(args->str_trans, result); + ret = nissy_frommoves(args->str_moves, result); print_cube_result(ret, result); return ret; } static int64_t -readcube_exec(args_t *args) -{ - char result[22]; - int64_t ret; - - ret = nissy_readcube(args->str_format, args->str_cube, result); - print_cube_result(ret, result); - - return ret; -} - -static int64_t -writecube_exec(args_t *args) -{ - char result[PRINTCUBE_BUFFER_SIZE]; - int64_t ret; - - ret = nissy_writecube(args->str_format, args->str_cube, result); - print_str_result(ret, result); - - return ret; -} - -static int64_t -convertcube_exec(args_t *args) +convert_exec(args_t *args) { char result[PRINTCUBE_BUFFER_SIZE]; int64_t ret; - ret = nissy_convertcube( + ret = nissy_convert( args->str_format_in, args->str_format_out, args->str_cube, result); print_str_result(ret, result); @@ -275,14 +247,17 @@ gendata_exec(args_t *args) if (tablepaths[i] == NULL) { fprintf(stderr, "Cannot write data to file\n"); + fclose(file); return -2; } size = nissy_datasize(args->str_solver, args->str_options); + if (size < 0) { fprintf(stderr, "Unknown error in retrieving data size" "(make sure solver is valid)\n"); + fclose(file); return -3; } @@ -291,19 +266,23 @@ gendata_exec(args_t *args) ret = nissy_gendata(args->str_solver, args->str_options, buf); if (ret < 0) { fprintf(stderr, "Unknown error in generating data\n"); + fclose(file); free(buf); return -4; } if (ret != size) { - fprintf(stderr, "Unknown error: unexpected data size\n"); + fprintf(stderr, "Unknown error: unexpected data size " + "(got %zu, expected %zu)\n", ret, size); + fclose(file); free(buf); return -5; } written = fwrite(buf, size, 1, file); + fclose(file); free(buf); - if (written != (int64_t)size) { + if (written != 1) { fprintf(stderr, "Error: data was generated correctly, but could not be " "written to file (generated %" PRId64 " bytes, written " @@ -311,7 +290,6 @@ gendata_exec(args_t *args) return -6; } - args->datasize = size; fprintf(stderr, "Data written to %s\n", path); return 0; @@ -323,7 +301,7 @@ solve_exec(args_t *args) int i; FILE *file; char *buf, solutions[SOLUTIONS_BUFFER_SIZE], path[MAX_PATH_LENGTH]; - int64_t ret, gendata_ret; + int64_t ret, gendata_ret, size; size_t read; for (i = 0; tablepaths[i] != NULL; i++) { @@ -344,23 +322,30 @@ solve_exec(args_t *args) } /* Ugh, this is not elegant TODO */ - for (i = 0; tablepaths[i] != NULL; i++) { - strcpy(path, tablepaths[i]); - strcat(path, args->str_solver); - file = fopen(path, "rb"); - if (file != NULL) - break; + if (file == NULL) { + for (i = 0; tablepaths[i] != NULL; i++) { + strcpy(path, tablepaths[i]); + strcat(path, args->str_solver); + file = fopen(path, "rb"); + if (file != NULL) + break; + } } if (tablepaths[i] == NULL) { fprintf(stderr, "Error: data file not found\n"); + fclose(file); return -1; } - buf = malloc(args->datasize); - read = fread(buf, args->datasize, 1, file); - if (read != args->datasize) { - fprintf(stderr, "Error reading data from file\n"); + size = nissy_datasize(args->str_solver, args->str_options); + buf = malloc(size); + read = fread(buf, size, 1, file); + fclose(file); + if (read != 1) { + fprintf(stderr, "Error reading data from file: " + "fread() returned %zu instead of 1 when attempting to" + "read %" PRId64 " bytes from file %s\n", read, size, path); return -2; } @@ -413,7 +398,7 @@ parse_args(int argc, char **argv, args_t *args) options[j].name); return 1; } - if (!options[j].set(n, argv+i, args)) { + if (!options[j].set(n, argv+i+1, args)) { fprintf(stderr, "Error parsing arguments for option %s\n", options[j].name); @@ -448,7 +433,8 @@ parse_int64(char *argv, int64_t *result) { *result = strtoll(argv, NULL, 10); - return errno != 0; + /* TODO: figure out how errno works and use it */ + return true; } static bool @@ -565,11 +551,22 @@ set_maxsolutions(int argc, char **argv, args_t *args) return parse_int64(argv[0], &args->maxsolutions); } +void log_stderr(const char *str, ...) +{ + va_list args; + + va_start(args, str); + vfprintf(stderr, str, args); + va_end(args); +} + int main(int argc, char **argv) { int parse_error; args_t args; + nissy_setlogger(log_stderr); + parse_error = parse_args(argc-1, argv+1, &args); if (parse_error) return parse_error; diff --git a/src/cube_public.h b/src/cube_public.h @@ -89,7 +89,7 @@ nissy_frommoves( } int64_t -nissy_convertcube( +nissy_convert( const char *format_in, const char *format_out, const char *cube_string, @@ -155,6 +155,7 @@ nissy_solve( { cube_t c; int64_t ret; + int h; c = readcube_B32(cube); @@ -188,8 +189,12 @@ nissy_solve( return -1; } - if (!strcmp(solver, "h48")) { - LOG("h48 solver not implemented yet\n"); + /* TODO define and use solve_options_t */ + if (!strcmp(solver, "H48")) { + h = atoi(options); /* TODO: better parsing */ + ret = solve_h48( + c, minmoves, maxmoves, maxsolutions, + (uint8_t)h, data, solutions); ret = -1; } else if (!strcmp(solver, "simple")) { ret = solve_simple( diff --git a/src/solve_generic.h b/src/solve_generic.h @@ -21,7 +21,7 @@ solve_generic_appendsolution(dfsarg_generic_t *arg) { int strl; - strl = writemoves(arg->moves, arg->depth, *arg->nextsol); + strl = writemoves(arg->moves, arg->nmoves, *arg->nextsol); LOG("Solution found: %s\n", *arg->nextsol); *arg->nextsol += strl; **arg->nextsol = '\n'; @@ -53,7 +53,6 @@ solve_generic_dfs(dfsarg_generic_t *arg) return 1; } - /* memcpy(&nextarg, arg, sizeof(dfsarg_generic_t)); */ nextarg = *arg; nextarg.nmoves = arg->nmoves + 1; for (m = 0, ret = 0; m < 18; m++) { diff --git a/src/solve_h48.h b/src/solve_h48.h @@ -11,6 +11,8 @@ #define COCLASS(x) (((x) & COCLASS_MASK) >> UINT32_C(16)) #define TTREP_MASK (UINT32_C(0xFF) << UINT32_C(8)) #define TTREP(x) (((x) & TTREP_MASK) >> UINT32_C(8)) +#define CBOUND_MASK UINT32_C(0xFF) +#define CBOUND(x) ((x) & CBOUND_MASK) #define H48_ESIZE(h) ((_12c4 * _8c4) << (int64_t)(h)) #define ESEP_IND(i) ((uint32_t)(i) / UINT32_C(8)) @@ -19,6 +21,8 @@ #define VISITED_IND(i) ((uint32_t)(i) / UINT32_C(8)) #define VISITED_MASK(i) (UINT32_C(1) << ((uint32_t)(i) % UINT32_C(8))) +#define MAX_SOLUTION_LENGTH 20 + typedef struct { cube_t cube; uint8_t depth; @@ -39,6 +43,20 @@ typedef struct { cube_t *crep; } bfsarg_esep_t; +typedef struct { + cube_t cube; + cube_t inverse; + int8_t nmoves; + int8_t depth; + uint8_t moves[MAX_SOLUTION_LENGTH]; + int64_t *nsols; + int64_t maxsolutions; + uint8_t h; + uint32_t *cocsepdata; + uint32_t *h48data; + char **nextsol; +} dfsarg_solveh48_t; + _static_inline int64_t coord_h48(cube_t, const uint32_t *, uint8_t); _static_inline int64_t coord_h48_edges(cube_t, int64_t, uint8_t, uint8_t); _static_inline cube_t invcoord_h48(int64_t, const cube_t *, uint8_t); @@ -53,6 +71,13 @@ _static_inline void set_visited(uint8_t *, int64_t); _static_inline uint8_t get_esep_pval(const uint32_t *, int64_t); _static_inline void set_esep_pval(uint32_t *, int64_t, uint8_t); +_static void solve_h48_appendsolution(dfsarg_solveh48_t *); +_static_inline int8_t get_h48_cdata(cube_t, uint32_t *, uint32_t *); +_static_inline int8_t get_h48_bound(cube_t, uint32_t, uint8_t, uint32_t *); +_static_inline bool solve_h48_stop(dfsarg_solveh48_t *); +_static int64_t solve_h48_dfs(dfsarg_solveh48_t *); +_static int64_t solve_h48(cube_t, int8_t, int8_t, int8_t, uint8_t, const void *, char *); + _static_inline int64_t coord_h48(cube_t c, const uint32_t *cocsepdata, uint8_t h) { @@ -238,8 +263,11 @@ gendata_h48(void *buf, uint8_t h, uint8_t maxdepth) cube_t crep[COCSEP_CLASSES]; size_t cocsepsize, infosize; + /* TODO: move info at start of tables (all tables!) */ infosize = 4 * maxdepth; cocsepsize = gendata_cocsep(buf, selfsim, crep); + infosize = 88; + if (buf == NULL) goto gendata_h48_return_size; @@ -272,7 +300,6 @@ gendata_h48(void *buf, uint8_t h, uint8_t maxdepth) } info[0] = arg.depth-1; - infosize = 4 * (size_t)(info[0] + 2); LOG("h48 pruning table computed\n"); LOG("Maximum pruning value: %" PRIu32 "\n", info[0]); @@ -353,3 +380,148 @@ set_esep_pval(uint32_t *buf32, int64_t i, uint8_t val) buf32[ESEP_IND(i)] = (buf32[ESEP_IND(i)] & (~ESEP_MASK(i))) | (val << ESEP_SHIFT(i)); } + +_static void +solve_h48_appendsolution(dfsarg_solveh48_t *arg) +{ + int strl; + + strl = writemoves(arg->moves, arg->nmoves, *arg->nextsol); + LOG("Solution found: %s\n", *arg->nextsol); + *arg->nextsol += strl; + **arg->nextsol = '\n'; + (*arg->nextsol)++; + (*arg->nsols)++; +} + +_static_inline int8_t +get_h48_cdata(cube_t cube, uint32_t *cocsepdata, uint32_t *cdata) +{ + int64_t coord; + + coord = coord_cocsep(cube); + *cdata = cocsepdata[coord]; + + return CBOUND(*cdata); +} + +_static_inline int8_t +get_h48_bound(cube_t cube, uint32_t cdata, uint8_t h, uint32_t *h48data) +{ + int64_t coord; + + coord = coord_h48_edges(cube, COCLASS(cdata), TTREP(cdata), h); + return get_esep_pval(h48data, coord); +} + +_static_inline bool +solve_h48_stop(dfsarg_solveh48_t *arg) +{ + uint32_t data, data_inv; + int8_t bound; + + bound = get_h48_cdata(arg->cube, arg->cocsepdata, &data); + if (bound + arg->nmoves > arg->depth) + return true; + + bound = get_h48_cdata(arg->inverse, arg->cocsepdata, &data_inv); + if (bound + arg->nmoves > arg->depth) + return true; + +/* + bound = get_h48_bound(arg->cube, data, arg->h, arg->h48data); +LOG("Using pval %" PRId8 "\n", bound); + if (bound + arg->nmoves > arg->depth) + return true; + + bound = get_h48_bound(arg->inverse, data_inv, arg->h, arg->h48data); + if (bound + arg->nmoves > arg->depth) + return true; +*/ + + return false; +} + +_static int64_t +solve_h48_dfs(dfsarg_solveh48_t *arg) +{ + dfsarg_solveh48_t nextarg; + int64_t ret; + uint8_t m; + + if (*arg->nsols == arg->maxsolutions) + return 0; + + if (solve_h48_stop(arg)) + return 0; + + if (issolved(arg->cube)) { + if (arg->nmoves != arg->depth) + return 0; + solve_h48_appendsolution(arg); + return 1; + } + + /* TODO: avoid copy, change arg and undo changes after recursion */ + nextarg = *arg; + nextarg.nmoves = arg->nmoves + 1; + ret = 0; + for (m = 0; m < 18; m++) { + nextarg.moves[arg->nmoves] = m; + if (!allowednextmove(nextarg.moves, nextarg.nmoves)) { + /* If a move is not allowed, neither are its 180 + * and 270 degree variations */ + m += 2; + continue; + } + nextarg.cube = move(arg->cube, m); + nextarg.inverse = inverse(nextarg.cube); /* TODO: use premove */ + ret += solve_h48_dfs(&nextarg); + } + + return ret; +} + +_static int64_t +solve_h48( + cube_t cube, + int8_t minmoves, + int8_t maxmoves, + int8_t maxsolutions, + uint8_t h, + const void *data, + char *solutions +) +{ + int64_t nsols; + dfsarg_solveh48_t arg; + + arg = (dfsarg_solveh48_t) { + .cube = cube, + .inverse = inverse(cube), + .nsols = &nsols, + .maxsolutions = maxsolutions, + .h = h, + .cocsepdata = (uint32_t *)data, + .h48data = ((uint32_t *)data) + COCSEP_FULLSIZE / 4, + .nextsol = &solutions + }; + + nsols = 0; + for (arg.depth = minmoves; + arg.depth <= maxmoves && nsols < maxsolutions; + arg.depth++) + { + LOG("Found %" PRId64 " solutions, searching at depth %" + PRId8 "\n", nsols, arg.depth); + arg.nmoves = 0; + solve_h48_dfs(&arg); + } + +/* +for (int64_t i = 0; i < 4; i++) +LOG("Data for coord = %" PRId64 ": %" PRIu8 "\n", +i, get_esep_pval(arg.h48data, i)); +*/ + return nsols; +} diff --git a/test/103_gendata_h48/00_h_0.out b/test/103_gendata_h48/00_h_0.out @@ -1,4 +1,4 @@ -59903545 +59903605 cocsepdata: Classes: 3393 diff --git a/test/103_gendata_h48/01_h_1.out b/test/103_gendata_h48/01_h_1.out @@ -1,4 +1,4 @@ -118687270 +118687330 cocsepdata: Classes: 3393