commit a3a477870cdbdb4b9c4c8b403c09b13502291234
parent b80b1b78901154240bc5d2ab6f5ea4465bf7fa67
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date: Sat, 19 Oct 2024 19:17:13 +0200
Finished Python module
Diffstat:
5 files changed, 304 insertions(+), 17 deletions(-)
diff --git a/README.md b/README.md
@@ -182,8 +182,14 @@ From here you can call the library functions directly, for example:
'ASTUGFBH=DACXEZGBLIKF'
```
-Please note: as this is work in progress, not all functions are currently
-available from the Python module.
+Some methods have a different signature in the Pythn module than in
+libnissy: for example `solve()` returns the solutions as a list of
+strings instead of writing them to a return parameter buffer.
+You can access the documentation for the Python module from within
+a Python interpreter with `help(nissy)`. Cross-check this documentation
+with the comments in nissy.h for more details.
+
+Please note: the support for the Python module is still rudimentary.
## Cube formats
diff --git a/python/example.py b/python/example.py
@@ -0,0 +1,37 @@
+# Small example of nissy_python_module usage
+
+# Compile the python module with "make python", then run this from the main
+# folder containing nissy_python_module.so
+
+# Append the main folder to the python path so we can load the module
+from sys import path
+path.append("./")
+
+# Import with a nicer name
+import nissy_python_module as nissy
+
+# Choose the solver you prefer
+solver = "h48h0k4"
+
+# Load the pruning table from file
+data = bytearray(open("tables/" + solver, "rb").read())
+
+# If you have not generated the table yet, you can do so:
+# data = nissy.gendata("h48h0k4")
+
+# Get a scrambled cube
+cube = nissy.applymoves(nissy.solved_cube, "U F R2");
+
+# Solve!
+solutions = nissy.solve(cube, solver, nissy.nissflag_normal, 0, 9, 3, -1, data)
+
+# Print the solutions, one per line
+print("Found ", len(solutions), " solutions:")
+for s in solutions:
+ print(s)
+
+# You can use help(nissy) for more info about the available methods:
+# help(nissy)
+
+# Or help(nissy.methodname) if you want to know more about a specific method:
+# help(nissy.solve)
diff --git a/python/nissy_module.c b/python/nissy_module.c
@@ -5,12 +5,17 @@
#include "../src/nissy.h"
#define MAX_CUBE_STR_LEN 1024 /* Update when adding formats */
+#define MAX_SOLUTIONS_SIZE 250000
static bool
check_error(long long err)
{
char err_string[255];
+ /* A positive value always denotes a success */
+ if (err > 0)
+ return true;
+
switch (err) {
case NISSY_OK: /* Fallthrough */
case NISSY_WARNING_UNSOLVABLE:
@@ -58,6 +63,18 @@ long_result(long long result)
return PyLong_FromLong(result);
}
+
+PyDoc_STRVAR(compose_doc,
+"compose(cube, permutation)\n"
+"--\n\n"
+"Apply 'permutation' on 'cube'.\n"
+"\n"
+"Parameters:\n"
+" - cube: a cube in B32 format\n"
+" - permutation: another cube in B32 format\n"
+"\n"
+"Returns: the resulting cube string in B32 format\n"
+);
static PyObject *
compose(PyObject *self, PyObject *args)
{
@@ -72,6 +89,16 @@ compose(PyObject *self, PyObject *args)
return string_result(err, result);
}
+PyDoc_STRVAR(inverse_doc,
+"inverse(cube)\n"
+"--\n\n"
+"Invert 'cube'.\n"
+"\n"
+"Parameters:\n"
+" - cube: a cube in B32 format\n"
+"\n"
+"Returns: the inverse cube in B32 format\n"
+);
static PyObject *
inverse(PyObject *self, PyObject *args)
{
@@ -86,6 +113,17 @@ inverse(PyObject *self, PyObject *args)
return string_result(err, result);
}
+PyDoc_STRVAR(applymoves_doc,
+"applymoves(cube, moves)\n"
+"--\n\n"
+"Apply 'moves' to 'cube'.\n"
+"\n"
+"Parameters:\n"
+" - cube: a cube in B32 format\n"
+" - moves: the moves to apply on the cube\n"
+"\n"
+"Returns: the resulting cube in B32 format\n"
+);
static PyObject *
applymoves(PyObject *self, PyObject *args)
{
@@ -100,6 +138,19 @@ applymoves(PyObject *self, PyObject *args)
return string_result(err, result);
}
+PyDoc_STRVAR(applytrans_doc,
+"applytrans(cube, transformation)\n"
+"--\n\n"
+"Apply 'transformation' to 'cube'.\n"
+"\n"
+"Parameters:\n"
+" - cube: a cube in B32 format\n"
+" - transformation: the transformation to apply on the cube, formatted as\n"
+" (rotation|mirrored) (2 letters)\n"
+" for example 'mirrored ur' or 'rotation lf'\n"
+"\n"
+"Returns: the resulting cube in B32 format\n"
+);
static PyObject *
applytrans(PyObject *self, PyObject *args)
{
@@ -114,6 +165,18 @@ applytrans(PyObject *self, PyObject *args)
return string_result(err, result);
}
+PyDoc_STRVAR(convert_doc,
+"convert(fin, fout, cube)\n"
+"--\n\n"
+"Convert 'cube' from format 'fin' to format 'fout'.\n"
+"\n"
+"Parameters:\n"
+" - fin: the format in which 'cube' is given\n"
+" - fout: the format to which 'cube' has to be converted\n"
+" - cube: a cube in B32 format\n"
+"\n"
+"Returns: 'cube' in 'fout' format\n"
+);
static PyObject *
convert(PyObject *self, PyObject *args)
{
@@ -128,6 +191,20 @@ convert(PyObject *self, PyObject *args)
return string_result(err, result);
}
+PyDoc_STRVAR(getcube_doc,
+"getcube(ep, eo, cp, co, options)\n"
+"--\n\n"
+"Constructs the cube from the given coordinates and options\n"
+"\n"
+"Parameters:\n"
+" - ep: the edge permutation coordinate\n"
+" - eo: the edge orientation coordinate\n"
+" - cp: the corner permutation coordinate\n"
+" - co: the corner orientation coordinate\n"
+" - options: a string, for example \"fix\"\n"
+"\n"
+"Returns: the cube constructed from the given coordinates\n"
+);
static PyObject *
getcube(PyObject *self, PyObject *args)
{
@@ -142,6 +219,16 @@ getcube(PyObject *self, PyObject *args)
return string_result(err, result);
}
+PyDoc_STRVAR(datasize_doc,
+"datasize(solver)\n"
+"--\n\n"
+"Returns the size of the data (pruning table) for the given solver\n"
+"\n"
+"Parameters:\n"
+" - solver: the name of the solver\n"
+"\n"
+"Returns: the size of the data for the solver, in bytes\n"
+);
static PyObject *
datasize(PyObject *self, PyObject *args)
{
@@ -155,23 +242,168 @@ datasize(PyObject *self, PyObject *args)
return long_result(result);
}
-/* TODO: gendata, checkdata and solve and countmoves */
+PyDoc_STRVAR(gendata_doc,
+"gendata(solver)\n"
+"--\n\n"
+"Generates the data (pruning table) for the given solver\n"
+"\n"
+"Parameters:\n"
+" - solver: the name of the solver\n"
+"\n"
+"Returns: a bytearray containing the data for the solver\n"
+);
+static PyObject *
+gendata(PyObject *self, PyObject *args)
+{
+ long long size, err;
+ const char *solver;
+ char *buf;
+
+ if (!PyArg_ParseTuple(args, "s", &solver))
+ return NULL;
+
+ size = nissy_datasize(solver);
+ if (!check_error(size))
+ return NULL;
+
+ buf = PyMem_Malloc(size);
+
+ Py_BEGIN_ALLOW_THREADS
+ err = nissy_gendata(solver, size, buf);
+ Py_END_ALLOW_THREADS
+
+ if (check_error(err))
+ return PyByteArray_FromStringAndSize(buf, size);
+ else
+ return NULL;
+}
+
+PyDoc_STRVAR(checkdata_doc,
+"checkdata(data)\n"
+"--\n\n"
+"Checks if the data (pruning table) given is valid or not\n"
+"\n"
+"Parameters:\n"
+" - data: a bytearray containing the data for a solver"
+"\n"
+"Returns: true if the data is valid, false otherwise\n"
+);
+PyObject *
+checkdata(PyObject *self, PyObject *args)
+{
+ long long result;
+ PyByteArrayObject *data;
+
+ if (!PyArg_ParseTuple(args, "Y", &data))
+ return NULL;
+
+ result = nissy_checkdata(data->ob_alloc, data->ob_bytes);
+
+ if (check_error(result))
+ return Py_True;
+ else
+ return Py_False;
+}
+
+PyDoc_STRVAR(solve_doc,
+"solve(cube, solver, nissflag, minmoves, maxmoves, maxsolutions, optimal, data)\n"
+"--\n\n"
+"Solves the given 'cube' with the given 'solver' and other parameters."
+"See the documentation for libnissy (in nissy.h) for details.\n"
+"\n"
+"Parameters:\n"
+" - cube: a cube in B32 format\n"
+" - solver: the solver to use\n"
+" - minmoves: the minimum number of moves to use\n"
+" - maxmoves: the maximum number of moves to use\n"
+" - maxsolution: the maximum number of solutions to return\n"
+" - optimal: the largest number of moves from the shortest solution"
+"(set to -1 to ignore)\n"
+" - data: a bytearray containing the data for the solver\n"
+"\n"
+"Returns: a list with the solutions found\n"
+);
+PyObject *
+solve(PyObject *self, PyObject *args)
+{
+ long long result;
+ unsigned nissflag, minmoves, maxmoves, maxsolutions;
+ int optimal, i, j, k;
+ const char *cube, *solver;
+ char solutions[MAX_SOLUTIONS_SIZE];
+ long long stats[NISSY_SIZE_SOLVE_STATS];
+ PyByteArrayObject *data;
+ PyObject *list, *item;
+
+ if (!PyArg_ParseTuple(args, "ssIIIIiY", &cube, &solver, &nissflag,
+ &minmoves, &maxmoves, &maxsolutions, &optimal, &data))
+ return NULL;
+
+ Py_BEGIN_ALLOW_THREADS
+ result = nissy_solve(cube, solver, nissflag, minmoves, maxmoves,
+ maxsolutions, optimal, data->ob_alloc, data->ob_bytes,
+ MAX_SOLUTIONS_SIZE, solutions, stats);
+ Py_END_ALLOW_THREADS
+
+ if(!check_error(result)) {
+ return NULL;
+ } else {
+ list = PyList_New(result);
+ for (i = 0, j = 0, k = 0; solutions[i] != 0; i++) {
+ if (solutions[i] != '\n')
+ continue;
+ solutions[i] = 0;
+ item = PyUnicode_FromString(&solutions[k]);
+ PyList_SetItem(list, j, item);
+ j++;
+ k = i+1;
+ }
+ return list;
+ }
+}
+
+PyDoc_STRVAR(countmoves_doc,
+"countmoves(moves)\n"
+"--\n\n"
+"Count the moves\n"
+"\n"
+"Parameters:\n"
+" - moves: the moves to be counted\n"
+"\n"
+"Returns: the number of moves in HTM metric\n"
+);
+PyObject *
+countmoves(PyObject *self, PyObject *args)
+{
+ long long count;
+ const char *moves;
+
+ if (!PyArg_ParseTuple(args, "s", &moves))
+ return NULL;
+
+ count = nissy_countmoves(moves);
+ return long_result(count);
+}
static PyMethodDef nissy_methods[] = {
- { "compose", compose, METH_VARARGS, NULL },
- { "inverse", inverse, METH_VARARGS, NULL },
- { "applymoves", applymoves, METH_VARARGS, NULL },
- { "applytrans", applytrans, METH_VARARGS, NULL },
- { "convert", convert, METH_VARARGS, NULL },
- { "getcube", getcube, METH_VARARGS, NULL },
- { "datasize", datasize, METH_VARARGS, NULL },
+ { "compose", compose, METH_VARARGS, compose_doc },
+ { "inverse", inverse, METH_VARARGS, inverse_doc },
+ { "applymoves", applymoves, METH_VARARGS, applymoves_doc },
+ { "applytrans", applytrans, METH_VARARGS, applytrans_doc },
+ { "convert", convert, METH_VARARGS, convert_doc },
+ { "getcube", getcube, METH_VARARGS, getcube_doc },
+ { "datasize", datasize, METH_VARARGS, datasize_doc },
+ { "gendata", gendata, METH_VARARGS, gendata_doc },
+ { "checkdata", checkdata, METH_VARARGS, checkdata_doc },
+ { "solve", solve, METH_VARARGS, solve_doc },
+ { "countmoves", countmoves, METH_VARARGS, countmoves_doc },
{ NULL, NULL, 0, NULL }
};
static struct PyModuleDef nissy_python_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "nissy",
- .m_doc = NULL,
+ .m_doc = "python module for libnissy",
.m_size = -1,
.m_methods = nissy_methods,
.m_slots = NULL,
@@ -195,7 +427,13 @@ PyMODINIT_FUNC PyInit_nissy_python_module(void) {
nissy_setlogger(log_stdout);
module = PyModule_Create(&nissy_python_module);
- PyModule_AddStringConstant(module, "solvedcube", NISSY_SOLVED_CUBE);
+
+ PyModule_AddStringConstant(module, "solved_cube", NISSY_SOLVED_CUBE);
+ PyModule_AddIntConstant(module, "nissflag_normal", NISSY_NISSFLAG_NORMAL);
+ PyModule_AddIntConstant(module, "nissflag_inverse", NISSY_NISSFLAG_INVERSE);
+ PyModule_AddIntConstant(module, "nissflag_mixed", NISSY_NISSFLAG_MIXED);
+ PyModule_AddIntConstant(module, "nissflag_linear", NISSY_NISSFLAG_LINEAR);
+ PyModule_AddIntConstant(module, "nissflag_all", NISSY_NISSFLAG_ALL);
return module;
}
diff --git a/src/nissy.c b/src/nissy.c
@@ -71,7 +71,10 @@ checkdata(const char *buf, const tableinfo_t *info)
{
uint64_t distr[INFO_DISTRIBUTION_LEN];
- if (!strncmp(info->solver, "cocsep", 6)) {
+ if (info == NULL) {
+ LOG("checkdata: error reading table info\n");
+ return false;
+ } else if (!strncmp(info->solver, "cocsep", 6)) {
getdistribution_cocsep(
(uint32_t *)((char *)buf + INFOSIZE), distr);
} else if (!strncmp(info->solver, "h48", 3)) {
@@ -442,21 +445,23 @@ nissy_checkdata(
{
char *buf;
tableinfo_t info;
+ int64_t err;
for (buf = (char *)data;
- readtableinfo(data_size, buf, &info);
+ (err = readtableinfo(data_size, buf, &info)) == NISSY_OK;
buf += info.next, data_size -= info.next)
{
if (!checkdata(buf, &info)) {
- LOG("Error: data for %s is inconsistent with info!\n",
+ LOG("Error: data for solver '%s' is corrupted!\n",
info.solver);
return NISSY_ERROR_DATA;
}
+
if (info.next == 0)
break;
}
- return NISSY_OK;
+ return err;
}
long long
diff --git a/src/solvers/tables.h b/src/solvers/tables.h
@@ -35,7 +35,8 @@ readtableinfo(uint64_t buf_size, const char *buf, tableinfo_t *info)
if (buf_size < INFOSIZE) {
LOG("Error reading table: buffer size is too small "
- "(smaller than INFOSIZE = %" PRId64 ")\n", INFOSIZE);
+ "(given size %" PRIu64 " is smaller than INFOSIZE = %"
+ PRId64 ")\n", buf_size, INFOSIZE);
return NISSY_ERROR_BUFFER_SIZE;
}