nissy-core

The "engine" of nissy, including the H48 optimal solver.
git clone https://git.tronto.net/nissy-core
Download | Log | Files | Refs | README | LICENSE

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 }