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

nissy.c (11659B)


      1 #include <inttypes.h>
      2 #include <limits.h>
      3 #include <pthread.h>
      4 #include <stdatomic.h>
      5 #include <stdarg.h>
      6 #include <stdbool.h>
      7 #include <string.h>
      8 
      9 #include "nissy.h"
     10 #include "utils/utils.h"
     11 #include "arch/arch.h"
     12 #include "core/core.h"
     13 #include "solvers/solvers.h"
     14 
     15 int parse_h48_solver(const char *, uint8_t [static 1], uint8_t [static 1]);
     16 STATIC bool checkdata(const char *, const tableinfo_t *);
     17 STATIC bool distribution_equal(const uint64_t [static INFO_DISTRIBUTION_LEN],
     18     const uint64_t [static INFO_DISTRIBUTION_LEN], uint8_t);
     19 STATIC long long write_result(cube_t, char [static NISSY_SIZE_B32]);
     20 STATIC size_t my_strnlen(const char *, size_t); 
     21 
     22 #define GETCUBE_OPTIONS(S, F) { .option = S, .fix = F }
     23 struct {
     24 	char *option;
     25 	void (*fix)(long long *, long long *, long long *, long long *);
     26 } getcube_options[] = {
     27 	GETCUBE_OPTIONS("fix", getcube_fix),
     28 	GETCUBE_OPTIONS(NULL, NULL)
     29 };
     30 
     31 int
     32 parse_h48_solver(const char *buf, uint8_t h[static 1], uint8_t k[static 1])
     33 {
     34 	const char *fullbuf = buf;
     35 
     36 	buf += 3;
     37 
     38 	if (!strcmp(buf, "stats")) {
     39 		*h = 0;
     40 		*k = 4;
     41 		return 0;
     42 	}
     43 
     44 	if (*buf != 'h')
     45 		goto parse_h48_solver_error;
     46 	buf++;
     47 
     48 	*h = atoi(buf);
     49 
     50 	for ( ; *buf >= 0 + '0' && *buf <= 9 + '0'; buf++)
     51 		if (*buf == 0)
     52 			goto parse_h48_solver_error;
     53 
     54 	if (*buf != 'k')
     55 		goto parse_h48_solver_error;
     56 	buf++;
     57 
     58 	*k = atoi(buf);
     59 
     60 	return *h < 12 && (*k == 2 || (*k == 4 && *h == 0)) ? 0 : 1;
     61 
     62 parse_h48_solver_error:
     63 	*h = 0;
     64 	*k = 0;
     65 	LOG("Error parsing solver: must be in \"h48h*k*\" format"
     66 	    " or \"h48stats\", but got %s\n", fullbuf);
     67 	return -1;
     68 }
     69 
     70 STATIC bool
     71 checkdata(const char *buf, const tableinfo_t *info)
     72 {
     73 	uint64_t distr[INFO_DISTRIBUTION_LEN];
     74 
     75 	if (info == NULL || my_strnlen(info->solver, INFO_SOLVER_STRLEN)
     76 	    == INFO_SOLVER_STRLEN) {
     77 		LOG("checkdata: error reading table info\n");
     78 		return false;
     79 	} else if (!strncmp(info->solver, "cocsep", 6)) {
     80 		getdistribution_cocsep(
     81 		    (uint32_t *)((char *)buf + INFOSIZE), distr);
     82 	} else if (!strncmp(info->solver, "h48", 3)) {
     83 		getdistribution_h48((uint8_t *)buf + INFOSIZE, distr,
     84 		    info->h48h, info->bits);
     85 	} else {
     86 		LOG("checkdata: unknown solver %s\n", info->solver);
     87 		return false;
     88 	}
     89 
     90 	return distribution_equal(info->distribution, distr, info->maxvalue);
     91 }
     92 
     93 STATIC bool
     94 distribution_equal(
     95 	const uint64_t expected[static INFO_DISTRIBUTION_LEN],
     96 	const uint64_t actual[static INFO_DISTRIBUTION_LEN],
     97 	uint8_t maxvalue
     98 )
     99 {
    100 	int wrong;
    101 	uint8_t i;
    102 
    103 	for (i = 0, wrong = 0; i <= MAX(maxvalue, 20); i++) {
    104 		if (expected[i] != actual[i]) {
    105 			wrong++;
    106 			LOG("Value %" PRIu8 ": expected %" PRIu64 ", found %"
    107 			    PRIu64 "\n", i, expected[i], actual[i]);
    108 		}
    109 	}
    110 
    111 	return wrong == 0;
    112 }
    113 
    114 STATIC long long
    115 write_result(cube_t cube, char result[static NISSY_SIZE_B32])
    116 {
    117 	writecube("B32", cube, NISSY_SIZE_B32, result);
    118 
    119 	if (!issolvable(cube)) {
    120 		LOG("Warning: resulting cube is not solvable\n");
    121 		return NISSY_WARNING_UNSOLVABLE;
    122 	}
    123 
    124 	return NISSY_OK;
    125 }
    126 
    127 STATIC size_t
    128 my_strnlen(const char *str, size_t maxlen)
    129 {
    130 	size_t i;
    131 
    132 	for (i = 0; i < maxlen; i++)
    133 		if (str[i] == '\0')
    134 			return i;
    135 
    136 	return maxlen;
    137 }
    138 
    139 long long
    140 nissy_compose(
    141 	const char cube[static NISSY_SIZE_B32],
    142 	const char permutation[static NISSY_SIZE_B32],
    143 	char result[static NISSY_SIZE_B32]
    144 )
    145 {
    146 	cube_t c, p, res;
    147 	long long err;
    148 
    149 	c = readcube("B32", cube);
    150 
    151 	if (!isconsistent(c)) {
    152 		LOG("Error in nissy_compose: given cube is invalid\n");
    153 		err = NISSY_ERROR_INVALID_CUBE;
    154 		goto nissy_compose_error;
    155 	}
    156 
    157 	p = readcube("B32", permutation);
    158 
    159 	if (!isconsistent(p)) {
    160 		LOG("Error in nissy_compose: given permutation is invalid\n");
    161 		err = NISSY_ERROR_INVALID_CUBE;
    162 		goto nissy_compose_error;
    163 	}
    164 
    165 	res = compose(c, p);
    166 
    167 	if (!isconsistent(res)) {
    168 		LOG("Unknown error: resulting cube is invalid\n");
    169 		err = NISSY_ERROR_UNKNOWN;
    170 		goto nissy_compose_error;
    171 	}
    172 
    173 	return write_result(res, result);
    174 
    175 nissy_compose_error:
    176 	writecube("B32", ZERO_CUBE, NISSY_SIZE_B32, result);
    177 	return err;
    178 }
    179 
    180 long long
    181 nissy_inverse(
    182 	const char cube[static NISSY_SIZE_B32],
    183 	char result[static NISSY_SIZE_B32]
    184 )
    185 {
    186 	cube_t c, res;
    187 	long long err;
    188 
    189 	c = readcube("B32", cube);
    190 
    191 	if (iserror(c)) {
    192 		LOG("Error in nissy_inverse: given cube is invalid\n");
    193 		err = NISSY_ERROR_INVALID_CUBE;
    194 		goto nissy_inverse_error;
    195 	}
    196 
    197 	res = inverse(c);
    198 
    199 	if (!isconsistent(res)) {
    200 		LOG("Unknown error: inverted cube is invalid\n");
    201 		err = NISSY_ERROR_UNKNOWN;
    202 		goto nissy_inverse_error;
    203 	}
    204 
    205 	return write_result(res, result);
    206 
    207 nissy_inverse_error:
    208 	writecube("B32", ZERO_CUBE, NISSY_SIZE_B32, result);
    209 	return err;
    210 }
    211 
    212 long long
    213 nissy_applymoves(
    214 	const char cube[static NISSY_SIZE_B32],
    215 	const char *moves,
    216 	char result[static NISSY_SIZE_B32]
    217 )
    218 {
    219 	cube_t c, res;
    220 	long long err;
    221 
    222 	if (moves == NULL) {
    223 		LOG("Error: 'moves' argument is NULL\n");
    224 		err = NISSY_ERROR_NULL_POINTER;
    225 		goto nissy_applymoves_error;
    226 	}
    227 
    228 	c = readcube("B32", cube);
    229 
    230 	if (!isconsistent(c)) {
    231 		LOG("Error in nissy_applymoves: given cube is invalid\n");
    232 		err = NISSY_ERROR_INVALID_CUBE;
    233 		goto nissy_applymoves_error;
    234 	}
    235 
    236 	res = applymoves(c, moves);
    237 
    238 	if (!isconsistent(res)) {
    239 		/* Assume we got a reasonable error message from applymoves */
    240 		err = NISSY_ERROR_INVALID_MOVES;
    241 		goto nissy_applymoves_error;
    242 	}
    243 
    244 	return write_result(res, result);
    245 
    246 nissy_applymoves_error:
    247 	writecube("B32", ZERO_CUBE, NISSY_SIZE_B32, result);
    248 	return err;
    249 }
    250 
    251 long long
    252 nissy_applytrans(
    253 	const char cube[static NISSY_SIZE_B32],
    254 	const char transformation[static NISSY_SIZE_TRANSFORMATION],
    255 	char result[static NISSY_SIZE_B32]
    256 )
    257 {
    258 	cube_t c, res;
    259 	long long err;
    260 
    261 	c = readcube("B32", cube);
    262 
    263 	if (!isconsistent(c)) {
    264 		LOG("Error in nissy_applytrans: given cube is invalid\n");
    265 		err = NISSY_ERROR_INVALID_CUBE;
    266 		goto nissy_applytrans_error;
    267 	}
    268 
    269 	res = applytrans(c, transformation);
    270 
    271 	if (!isconsistent(res)) {
    272 		/* Assume we got a reasonable error message from applytrans */
    273 		err = NISSY_ERROR_INVALID_TRANS;
    274 		goto nissy_applytrans_error;
    275 	}
    276 
    277 	return write_result(res, result);
    278 
    279 nissy_applytrans_error:
    280 	writecube("B32", ZERO_CUBE, NISSY_SIZE_B32, result);
    281 	return err;
    282 }
    283 
    284 long long
    285 nissy_convert(
    286 	const char *format_in,
    287 	const char *format_out,
    288 	const char *cube_string,
    289         unsigned result_size,
    290 	char result[result_size]
    291 )
    292 {
    293 	cube_t c;
    294 	long long err;
    295 
    296 	if (format_in == NULL) {
    297 		LOG("Error: 'format_in' argument is NULL\n");
    298 		err = NISSY_ERROR_NULL_POINTER;
    299 		goto nissy_convert_error;
    300 	}
    301 
    302 	if (format_out == NULL) {
    303 		LOG("Error: 'format_out' argument is NULL\n");
    304 		err = NISSY_ERROR_NULL_POINTER;
    305 		goto nissy_convert_error;
    306 	}
    307 
    308 	if (cube_string == NULL) {
    309 		LOG("Error: 'cube_string' argument is NULL\n");
    310 		err = NISSY_ERROR_NULL_POINTER;
    311 		goto nissy_convert_error;
    312 	}
    313 
    314 	c = readcube(format_in, cube_string);
    315 
    316 	if (!isconsistent(c)) {
    317 		err = NISSY_ERROR_INVALID_CUBE;
    318 		goto nissy_convert_error;
    319 	}
    320 
    321 	return writecube(format_out, c, result_size, result);
    322 
    323 nissy_convert_error:
    324 	result[0] = '\0';
    325 	return err;
    326 }
    327 
    328 long long
    329 nissy_getcube(
    330 	long long ep,
    331 	long long eo,
    332 	long long cp,
    333 	long long co,
    334 	const char *options,
    335 	char result[static NISSY_SIZE_B32]
    336 )
    337 {
    338 	int i;
    339 	cube_t c;
    340 
    341 	if (options == NULL) {
    342 		LOG("Error: 'options' argument is NULL\n");
    343 		return NISSY_ERROR_NULL_POINTER;
    344 	}
    345 
    346 	for (i = 0; getcube_options[i].option != NULL; i++)
    347 		if (!strcmp(options, getcube_options[i].option))
    348 			getcube_options[i].fix(&ep, &eo, &cp, &co);
    349 
    350 	c = getcube(ep, eo, cp, co);
    351 
    352 	if (!isconsistent(c)) {
    353 		LOG("Error: could not get cube with ep=%" PRId64 ", eo=%"
    354 		    PRId64 ", cp=%" PRId64 ", co=%" PRId64 ".\n",
    355 		    ep, eo, cp, co);
    356 		return NISSY_ERROR_OPTIONS;
    357 	}
    358 
    359 	return write_result(c, result);
    360 }
    361 
    362 long long
    363 nissy_datasize(
    364 	const char *solver
    365 )
    366 {
    367 	if (solver == NULL) {
    368 		LOG("Error: 'solver' argument is NULL\n");
    369 		return NISSY_ERROR_NULL_POINTER;
    370 	}
    371 
    372 	/* gendata() handles a NULL *data as a "dryrun" request */
    373 	return nissy_gendata(solver, 0, NULL);
    374 }
    375 
    376 long long
    377 nissy_datainfo(
    378         uint64_t data_size,
    379 	const char data[data_size],
    380 	void (*write)(const char *, ...)
    381 )
    382 {
    383 	uint8_t i;
    384 	tableinfo_t info;
    385 	long long ret;
    386 
    387 	ret = readtableinfo(data_size, data, &info);
    388 	if (ret != 0)
    389 		return ret;
    390 
    391 	write("\n---------\n\n");
    392 	write("Table information for '%s'\n", info.solver);
    393 	write("\n");
    394 	write("Size:      %" PRIu64 " bytes\n", info.fullsize);
    395 	write("Entries:   %" PRIu64 " (%" PRIu8 " bits per entry)",
    396 	    info.entries, info.bits);
    397 	write("\n");
    398 
    399 	switch (info.type) {
    400 	case TABLETYPE_PRUNING:
    401 		write("\n");
    402 		write("Table distribution:\nValue\tPositions\n");
    403 		for (i = 0; i <= info.maxvalue; i++) {
    404 			write("%" PRIu8 "\t%" PRIu64 "\n",
    405 			    i + info.base, info.distribution[i]);
    406 		}
    407 		break;
    408 	case TABLETYPE_SPECIAL:
    409 		write("This is an ad-hoc table\n");
    410 		break;
    411 	default:
    412 		LOG("datainfo: unknown table type\n");
    413 		return NISSY_ERROR_DATA;
    414 	}
    415 
    416 	if (info.next != 0)
    417 		return nissy_datainfo(
    418 		    data_size - info.next, (char *)data + info.next, write);
    419 
    420 	write("\n---------\n");
    421 
    422 	return NISSY_OK;
    423 }
    424 
    425 long long
    426 nissy_gendata(
    427 	const char *solver,
    428 	unsigned long long data_size,
    429 	char data[data_size]
    430 )
    431 {
    432 	int p;
    433 	gendata_h48_arg_t arg;
    434 
    435 	if (solver == NULL) {
    436 		LOG("Error: 'solver' argument is NULL\n");
    437 		return NISSY_ERROR_NULL_POINTER;
    438 	}
    439 
    440 	arg.buf_size = data_size;
    441 	arg.buf = data;
    442 	if (!strncmp(solver, "h48", 3)) {
    443 		p = parse_h48_solver(solver, &arg.h, &arg.k);
    444 		arg.maxdepth = 20;
    445 		if (p != 0)
    446 			return NISSY_ERROR_UNKNOWN;
    447 		return gendata_h48(&arg);
    448 	} else {
    449 		LOG("gendata: unknown solver %s\n", solver);
    450 		return NISSY_ERROR_INVALID_SOLVER;
    451 	}
    452 }
    453 
    454 long long
    455 nissy_checkdata(
    456 	unsigned long long data_size,
    457 	const char data[data_size]
    458 )
    459 {
    460 	char *buf;
    461 	tableinfo_t info;
    462 	int64_t err;
    463 
    464 	for (buf = (char *)data;
    465 	     (err = readtableinfo(data_size, buf, &info)) == NISSY_OK;
    466 	     buf += info.next, data_size -= info.next)
    467 	{
    468 		if (!checkdata(buf, &info)) {
    469 			LOG("Error: data for solver '%s' is corrupted!\n",
    470 			    info.solver);
    471 			return NISSY_ERROR_DATA;
    472 		}
    473 
    474 		if (info.next == 0)
    475 			break;
    476 	}
    477 
    478 	return err;
    479 }
    480 
    481 long long
    482 nissy_solve(
    483 	const char cube[static NISSY_SIZE_B32],
    484 	const char *solver, 
    485 	unsigned nissflag,
    486 	unsigned minmoves,
    487 	unsigned maxmoves,
    488 	unsigned maxsols,
    489 	int optimal,
    490 	unsigned long long data_size,
    491 	const char data[data_size],
    492 	unsigned sols_size,
    493 	char sols[sols_size],
    494 	long long stats[static NISSY_SIZE_SOLVE_STATS]
    495 )
    496 {
    497 	cube_t c;
    498 	int p;
    499 	uint8_t h, k;
    500 
    501 	if (solver == NULL) {
    502 		LOG("Error: 'solver' argument is NULL\n");
    503 		return NISSY_ERROR_NULL_POINTER;
    504 	}
    505 
    506 	c = readcube_B32(cube);
    507 
    508 	if (!isconsistent(c)) {
    509 		LOG("solve: cube is invalid\n");
    510 		return NISSY_ERROR_INVALID_CUBE;
    511 	}
    512 
    513 	if (!issolvable(c)) {
    514 		LOG("solve: cube is not solvable\n");
    515 		return NISSY_ERROR_UNSOLVABLE_CUBE;
    516 	}
    517 
    518 	if (maxsols == 0) {
    519 		LOG("solve: 'maxsols' is 0, returning no solution\n");
    520 		return 0;
    521 	}
    522 
    523 	if (!strncmp(solver, "h48", 3)) {
    524 		if (!strcmp(solver, "h48stats"))
    525 			return solve_h48stats(c, maxmoves, data, sols);
    526 
    527 		p = parse_h48_solver(solver, &h, &k);
    528 		if (p != 0) {
    529 			LOG("solve: unknown solver %s\n", solver);
    530 			return NISSY_ERROR_INVALID_SOLVER;
    531 		} else {
    532 			return THREADS > 1 ?
    533 			    solve_h48_multithread(c, minmoves, maxmoves,
    534 			        maxsols, data_size, data, sols_size, sols, stats) :
    535 			    solve_h48(c, minmoves, maxmoves, maxsols,
    536 			        data_size, data, sols_size, sols, stats);
    537 		}
    538 	} else {
    539 		LOG("solve: unknown solver '%s'\n", solver);
    540 		return NISSY_ERROR_INVALID_SOLVER;
    541 	}
    542 }
    543 
    544 long long
    545 nissy_countmoves(
    546 	const char *moves
    547 )
    548 {
    549 	if (moves == NULL)
    550 		return NISSY_ERROR_NULL_POINTER;
    551 
    552 	return readmoves(moves, INT_MAX, NULL);
    553 }
    554 
    555 long long
    556 nissy_setlogger(
    557 	void (*log)(const char *, ...)
    558 )
    559 {
    560 	nissy_log = log;
    561 	return NISSY_OK;
    562 }