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

nissy.c (12235B)


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