nissy_module.c (11276B)
1 #define PY_SSIZE_T_CLEAN 2 #include <Python.h> 3 #include <stdbool.h> 4 5 #include "../src/nissy.h" 6 7 #define MAX_SOLUTIONS_SIZE 250000 8 9 static bool 10 check_error(long long err) 11 { 12 char err_string[255]; 13 14 /* A positive value always denotes a success */ 15 if (err > 0) 16 return true; 17 18 switch (err) { 19 case NISSY_OK: /* Fallthrough */ 20 case NISSY_WARNING_UNSOLVABLE: 21 return true; 22 case NISSY_ERROR_INVALID_CUBE: 23 case NISSY_ERROR_UNSOLVABLE_CUBE: /* Fallthrough */ 24 case NISSY_ERROR_INVALID_MOVES: 25 case NISSY_ERROR_INVALID_TRANS: 26 case NISSY_ERROR_INVALID_SOLVER: 27 case NISSY_ERROR_NULL_POINTER: 28 case NISSY_ERROR_BUFFER_SIZE: 29 case NISSY_ERROR_DATA: 30 case NISSY_ERROR_OPTIONS: 31 case NISSY_ERROR_UNKNOWN: 32 default: 33 sprintf(err_string, "Error from libnissy (%lld)", err); 34 PyErr_SetString(PyExc_Exception, err_string); 35 return false; 36 } 37 38 } 39 40 static PyObject * 41 string_result(long long err, const char *result) 42 { 43 return check_error(err) ? PyUnicode_FromString(result) : NULL; 44 } 45 46 static PyObject * 47 stringlist_result(long long err, char *result) 48 { 49 int i, j, k; 50 PyObject *list, *item; 51 52 if(!check_error(err)) { 53 return NULL; 54 } else { 55 list = PyList_New(err); 56 for (i = 0, j = 0, k = 0; result[i] != 0; i++) { 57 if (result[i] != '\n') 58 continue; 59 result[i] = 0; 60 item = PyUnicode_FromString(&result[k]); 61 PyList_SetItem(list, j, item); 62 j++; 63 k = i+1; 64 } 65 return list; 66 } 67 } 68 69 static PyObject * 70 string_result_free(long long err, char *result) 71 { 72 PyObject *ret; 73 74 ret = PyUnicode_FromString(result); 75 free(result); 76 77 return check_error(err) ? ret : NULL; 78 } 79 80 static PyObject * 81 long_result(long long result) 82 { 83 check_error(result); 84 return PyLong_FromLong(result); 85 } 86 87 PyDoc_STRVAR(inverse_doc, 88 "inverse(cube)\n" 89 "--\n\n" 90 "Invert 'cube'.\n" 91 "\n" 92 "Parameters:\n" 93 " - cube: a cube in\n" 94 "\n" 95 "Returns: the inverse cube\n" 96 ); 97 static PyObject * 98 inverse(PyObject *self, PyObject *args) 99 { 100 long long err; 101 const char *cube; 102 char result[NISSY_SIZE_CUBE]; 103 104 if (!PyArg_ParseTuple(args, "s", &cube)) 105 return NULL; 106 107 err = nissy_inverse(cube, result); 108 return string_result(err, result); 109 } 110 111 PyDoc_STRVAR(applymoves_doc, 112 "applymoves(cube, moves)\n" 113 "--\n\n" 114 "Apply 'moves' to 'cube'.\n" 115 "\n" 116 "Parameters:\n" 117 " - cube: a cube in\n" 118 " - moves: the moves to apply on the cube\n" 119 "\n" 120 "Returns: the resulting cube\n" 121 ); 122 static PyObject * 123 applymoves(PyObject *self, PyObject *args) 124 { 125 long long err; 126 const char *cube, *moves; 127 char result[NISSY_SIZE_CUBE]; 128 129 if (!PyArg_ParseTuple(args, "ss", &cube, &moves)) 130 return NULL; 131 132 err = nissy_applymoves(cube, moves, result); 133 return string_result(err, result); 134 } 135 136 PyDoc_STRVAR(applytrans_doc, 137 "applytrans(cube, transformation)\n" 138 "--\n\n" 139 "Apply 'transformation' to 'cube'.\n" 140 "\n" 141 "Parameters:\n" 142 " - cube: a cube\n" 143 " - transformation: the transformation to apply on the cube, formatted as\n" 144 " (rotation|mirrored) (2 letters)\n" 145 " for example 'mirrored ur' or 'rotation lf'\n" 146 "\n" 147 "Returns: the resulting cube\n" 148 ); 149 static PyObject * 150 applytrans(PyObject *self, PyObject *args) 151 { 152 long long err; 153 const char *cube, *trans; 154 char result[NISSY_SIZE_CUBE]; 155 156 if (!PyArg_ParseTuple(args, "ss", &cube, &trans)) 157 return NULL; 158 159 err = nissy_applytrans(cube, trans, result); 160 return string_result(err, result); 161 } 162 163 PyDoc_STRVAR(getcube_doc, 164 "getcube(ep, eo, cp, co, orientation, options)\n" 165 "--\n\n" 166 "Constructs the cube from the given coordinates and options\n" 167 "\n" 168 "Parameters:\n" 169 " - ep: the edge permutation coordinate\n" 170 " - eo: the edge orientation coordinate\n" 171 " - cp: the corner permutation coordinate\n" 172 " - co: the corner orientation coordinate\n" 173 " - orientation: the orientation of the cube\n" 174 " - options: a string, for example \"fix\"\n" 175 "\n" 176 "Returns: the cube constructed from the given coordinates\n" 177 ); 178 static PyObject * 179 getcube(PyObject *self, PyObject *args) 180 { 181 long long ep, eo, cp, co, or, err; 182 const char *options; 183 char result[NISSY_SIZE_CUBE]; 184 185 if (!PyArg_ParseTuple( 186 args, "LLLLLs", &ep, &eo, &cp, &co, &or, &options)) 187 return NULL; 188 189 err = nissy_getcube(ep, eo, cp, co, or, options, result); 190 return string_result(err, result); 191 } 192 193 PyDoc_STRVAR(solverinfo_doc, 194 "solverinfo(solver)\n" 195 "--\n\n" 196 "Returns the size and the short name of the data for the given solver\n" 197 "\n" 198 "Parameters:\n" 199 " - solver: the name of the solver\n" 200 "\n" 201 "Returns: a pair containing the size and the short name " 202 "of the data for the solver, in bytes\n" 203 ); 204 static PyObject * 205 solverinfo(PyObject *self, PyObject *args) 206 { 207 long long result; 208 const char *solver; 209 char buf[NISSY_SIZE_DATAID]; 210 PyObject *py_result, *py_buf; 211 212 if (!PyArg_ParseTuple(args, "s", &solver)) 213 return NULL; 214 215 result = nissy_solverinfo(solver, buf); 216 217 py_result = PyLong_FromLong(result); 218 py_buf = PyUnicode_FromString(buf); 219 return PyTuple_Pack(2, py_result, py_buf); 220 } 221 222 PyDoc_STRVAR(gendata_doc, 223 "gendata(solver)\n" 224 "--\n\n" 225 "Generates the data (pruning table) for the given solver\n" 226 "\n" 227 "Parameters:\n" 228 " - solver: the name of the solver\n" 229 "\n" 230 "Returns: a bytearray containing the data for the solver\n" 231 ); 232 static PyObject * 233 gendata(PyObject *self, PyObject *args) 234 { 235 long long size, err; 236 const char *solver; 237 char dataid[NISSY_SIZE_DATAID]; 238 unsigned char *buf; 239 240 if (!PyArg_ParseTuple(args, "s", &solver)) 241 return NULL; 242 243 size = nissy_solverinfo(solver, dataid); 244 if (!check_error(size)) 245 return NULL; 246 247 buf = PyMem_Malloc(size); 248 249 Py_BEGIN_ALLOW_THREADS 250 err = nissy_gendata(solver, size, buf); 251 Py_END_ALLOW_THREADS 252 253 if (check_error(err)) 254 return PyByteArray_FromStringAndSize((char *)buf, size); 255 else 256 return NULL; 257 } 258 259 PyDoc_STRVAR(checkdata_doc, 260 "checkdata(data)\n" 261 "--\n\n" 262 "Checks if the data (pruning table) given is valid or not\n" 263 "\n" 264 "Parameters:\n" 265 " - solver: the name of the solver\n" 266 " - data: a bytearray containing the data for a solver\n" 267 "\n" 268 "Returns: true if the data is valid, false otherwise\n" 269 ); 270 PyObject * 271 checkdata(PyObject *self, PyObject *args) 272 { 273 const char *solver; 274 long long result; 275 PyByteArrayObject *data; 276 277 if (!PyArg_ParseTuple(args, "sY", &solver, &data)) 278 return NULL; 279 280 result = nissy_checkdata( 281 solver, data->ob_alloc, (unsigned char *)data->ob_bytes); 282 283 if (check_error(result)) 284 return Py_True; 285 else 286 return Py_False; 287 } 288 289 PyDoc_STRVAR(solve_doc, 290 "solve(cube, solver, nissflag, minmoves, maxmoves, maxsolutions," 291 " optimal, threads, data)\n" 292 "--\n\n" 293 "Solves the given 'cube' with the given 'solver' and other parameters." 294 "See the documentation for libnissy (in nissy.h) for details.\n" 295 "\n" 296 "Parameters:\n" 297 " - cube: a cube\n" 298 " - solver: the solver to use\n" 299 " - minmoves: the minimum number of moves to use\n" 300 " - maxmoves: the maximum number of moves to use\n" 301 " - maxsolution: the maximum number of solutions to return\n" 302 " - optimal: the largest number of moves from the shortest solution\n" 303 " - threads: the number of threads to use (0 for default)\n" 304 " - data: a bytearray containing the data for the solver\n" 305 "\n" 306 "Returns: a list with the solutions found\n" 307 ); 308 PyObject * 309 solve(PyObject *self, PyObject *args) 310 { 311 long long result; 312 unsigned nissflag, minmoves, maxmoves, maxsolutions; 313 int optimal, threads; 314 const char *cube, *solver; 315 char solutions[MAX_SOLUTIONS_SIZE]; 316 long long stats[NISSY_SIZE_SOLVE_STATS]; 317 PyByteArrayObject *data; 318 319 if (!PyArg_ParseTuple(args, "ssIIIIIIY", &cube, &solver, &nissflag, 320 &minmoves, &maxmoves, &maxsolutions, &optimal, &threads, &data)) 321 return NULL; 322 323 Py_BEGIN_ALLOW_THREADS 324 result = nissy_solve(cube, solver, nissflag, minmoves, maxmoves, 325 maxsolutions, optimal, threads, data->ob_alloc, 326 (unsigned char *)data->ob_bytes, MAX_SOLUTIONS_SIZE, solutions, 327 stats, NULL, NULL); 328 Py_END_ALLOW_THREADS 329 330 return stringlist_result(result, solutions); 331 } 332 333 PyDoc_STRVAR(countmoves_doc, 334 "countmoves(moves)\n" 335 "--\n\n" 336 "Count the moves\n" 337 "\n" 338 "Parameters:\n" 339 " - moves: the moves to be counted\n" 340 "\n" 341 "Returns: the number of moves in HTM metric\n" 342 ); 343 PyObject * 344 countmoves(PyObject *self, PyObject *args) 345 { 346 long long count; 347 const char *moves; 348 349 if (!PyArg_ParseTuple(args, "s", &moves)) 350 return NULL; 351 352 count = nissy_countmoves(moves); 353 return long_result(count); 354 } 355 356 PyDoc_STRVAR(comparemoves_doc, 357 "comparemoves(moves1, moves2)\n" 358 "--\n\n" 359 "Compare the two move sequences\n" 360 "\n" 361 "Parameters:\n" 362 " - moves1: the first sequence of moves\n" 363 " - moves2: the second sequence of moves\n" 364 "\n" 365 "Returns: a string describing how the two moves sequences compare. " 366 "This can be one of:\n" 367 "\"EQUAL\" - The two sequences are equal up to swapping parallel moves\n" 368 "\"DIFFERENT\" - The two sequences are different\n" 369 ); 370 PyObject * 371 comparemoves(PyObject *self, PyObject *args) 372 { 373 long long cmp; 374 const char *m1, *m2; 375 376 if (!PyArg_ParseTuple(args, "ss", &m1, &m2)) 377 return NULL; 378 379 cmp = nissy_comparemoves(m1, m2); 380 if (!check_error(cmp)) 381 return NULL; 382 383 switch (cmp) { 384 case NISSY_COMPARE_MOVES_EQUAL: 385 return string_result(cmp, "EQUAL"); 386 case NISSY_COMPARE_MOVES_DIFFERENT: 387 return string_result(cmp, "DIFFERENT"); 388 default: 389 return long_result(cmp); 390 } 391 } 392 393 PyDoc_STRVAR(variations_doc, 394 "variations(moves, variation)\n" 395 "--\n\n" 396 "Find variations of a given move sequence\n" 397 "\n" 398 "Parameters:\n" 399 " - moves: the moves\n" 400 " - variation: the variation to apply, such as 'unniss' or 'lastqt'\n" 401 "\n" 402 "Returns: a list of move sequences, the variation of the given moves.\n" 403 ); 404 PyObject * 405 variations(PyObject *self, PyObject *args) 406 { 407 long long err; 408 const char *m, *v; 409 char result[MAX_SOLUTIONS_SIZE]; 410 411 if (!PyArg_ParseTuple(args, "ss", &m, &v)) 412 return NULL; 413 414 err = nissy_variations(m, v, MAX_SOLUTIONS_SIZE, result); 415 416 return stringlist_result(err, result); 417 } 418 419 static PyMethodDef nissy_methods[] = { 420 { "inverse", inverse, METH_VARARGS, inverse_doc }, 421 { "applymoves", applymoves, METH_VARARGS, applymoves_doc }, 422 { "applytrans", applytrans, METH_VARARGS, applytrans_doc }, 423 { "getcube", getcube, METH_VARARGS, getcube_doc }, 424 { "solverinfo", solverinfo, METH_VARARGS, solverinfo_doc }, 425 { "gendata", gendata, METH_VARARGS, gendata_doc }, 426 { "checkdata", checkdata, METH_VARARGS, checkdata_doc }, 427 { "solve", solve, METH_VARARGS, solve_doc }, 428 { "countmoves", countmoves, METH_VARARGS, countmoves_doc }, 429 { "comparemoves", comparemoves, METH_VARARGS, comparemoves_doc }, 430 { "variations", variations, METH_VARARGS, variations_doc }, 431 { NULL, NULL, 0, NULL } 432 }; 433 434 static struct PyModuleDef nissy = { 435 .m_base = PyModuleDef_HEAD_INIT, 436 .m_name = "nissy", 437 .m_doc = "python module for libnissy", 438 .m_size = -1, 439 .m_methods = nissy_methods, 440 .m_slots = NULL, 441 .m_traverse = NULL, 442 .m_clear = NULL, 443 .m_free = NULL 444 }; 445 446 static void 447 log_stdout(const char *str, void *unused) 448 { 449 fprintf(stderr, "%s", str); 450 } 451 452 PyMODINIT_FUNC PyInit_nissy(void) { 453 PyObject *module; 454 455 nissy_setlogger(log_stdout, NULL); 456 module = PyModule_Create(&nissy); 457 458 PyModule_AddStringConstant(module, "solved_cube", NISSY_SOLVED_CUBE); 459 PyModule_AddIntConstant(module, "nissflag_normal", NISSY_NISSFLAG_NORMAL); 460 PyModule_AddIntConstant(module, "nissflag_inverse", NISSY_NISSFLAG_INVERSE); 461 PyModule_AddIntConstant(module, "nissflag_mixed", NISSY_NISSFLAG_MIXED); 462 PyModule_AddIntConstant(module, "nissflag_linear", NISSY_NISSFLAG_LINEAR); 463 PyModule_AddIntConstant(module, "nissflag_all", NISSY_NISSFLAG_ALL); 464 465 return module; 466 }