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