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 }