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 (13839B)


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