h48

A prototype for an optimal Rubik's cube solver, work in progress.
git clone https://git.tronto.net/h48
Download | Log | Files | Refs | README | LICENSE

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 }