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 }