Starts implementing RPNIterExpr python object

This commit is contained in:
Yann Weber 2023-06-07 10:46:52 +02:00
commit d535b5c64c
4 changed files with 462 additions and 8 deletions

View file

@ -1,6 +1,36 @@
#include "python_if.h"
static PyStructSequence_Field params_fields[] = {
{ .name = "pos_flag",
.doc = "Position flag, indicating coordinate system",
},
{ .name = "res_flag",
.doc = "Result flag, indicate how to handle expr results",
},
{ .name = "size_lim",
.doc = "Size limit, an array indicating the space size (depends \
on pos_flag",
},
{
.name = "const_values",
.doc = "Constant values to use when setting value in space \
(expr gives the position, and we set this value",
},
{ NULL, NULL },
};
PyStructSequence_Desc rpnif_params_desc = {
.name = "RPNIterParams",
.doc = "Named tuple for RPNIter parameters",
.n_in_sequence = (sizeof(params_fields) / sizeof(*params_fields))-1,
.fields = params_fields,
};
PyTypeObject rpnif_params_SeqDesc;
PyMethodDef RPNIterExpr_methods[] = {
{"get_params", (PyCFunction)rpnif_get_params, METH_NOARGS,
"Get a named tuple with parameters"},
{"__getstate__", (PyCFunction)rpnif_getstate, METH_NOARGS,
"Pickling method. Return a bytes repr of tokenized expression \
and the stack state."},
@ -17,6 +47,12 @@ PyGetSetDef RPNIterExpr_getset[] = {
{NULL}
};
// Allows manipulation from numpy
static PyBufferProcs RPNIterExpr_as_buffer = {
(getbufferproc)rpnif_getbuffer,
(releasebufferproc)rpnif_releasebuffer,
};
PyTypeObject RPNIterExprType = {
PyVarObject_HEAD_INIT(NULL, 0)
"pyrpn.RPNIterExpr", /* tp_name */
@ -36,7 +72,7 @@ PyTypeObject RPNIterExprType = {
rpnif_str, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
&RPNIterExpr_as_buffer, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_BASETYPE, /* tp_flags */
"RPN expression evaluator", /* tp_doc */
@ -78,21 +114,22 @@ PyObject* rpnif_new(PyTypeObject *subtype, PyObject *args, PyObject* kwds)
int rpnif_init(PyObject *self, PyObject *args, PyObject *kwds)
{
/**@todo TODO write the function */
PyRPNIterExpr_t *expr_self;
char *names[] = {"pos_flag", "res_flag", "lim", "value", "stack_size", NULL};
char *names[] = {"pos_flag", "res_flag", "size_lim", "const_values", "stack_size", NULL};
unsigned short pos_flag, res_flag, stack_size;
PyObject *lim, *value;
PyObject *lim_obj, *const_values_obj;
int ndim;
char err_str[256];
expr_self = (PyRPNIterExpr_t*)self;
stack_size = 16;
value = lim = NULL;
const_values_obj = lim_obj = NULL;
expr_self->rif = NULL;
if(!PyArg_ParseTupleAndKeywords(args, kwds, "HHO|OH:RPNIterExpr.__init__", names,
&pos_flag, &res_flag, lim, value, &stack_size))
&pos_flag, &res_flag, &lim_obj, &const_values_obj, &stack_size))
{
return -1;
}
@ -106,13 +143,213 @@ int rpnif_init(PyObject *self, PyObject *args, PyObject *kwds)
PyErr_SetString(PyExc_ValueError, err_str);
return -1;
}
//Check & convert lim
//Check & convert value
// Checks flags & fetch expected sizes for size_lim & const_values
short expt_sizes[2];
if(rpn_if_sizes_from_flag(pos_flag, res_flag, expt_sizes) < 0)
{
if(expt_sizes[0] < 0)
{
PyErr_SetString(PyExc_ValueError,
"Invalid position flag given");
}
else
{
PyErr_SetString(PyExc_ValueError,
"Invalid result flag given");
}
return -1;
}
//Check & convert lim
PyObject *tmp;
tmp = lim_obj;
if(!PyTuple_Check(tmp))
{
PyErr_SetString(PyExc_ValueError,
"Invalid type for size_lim argument");
return -1;
}
Py_ssize_t lim_obj_sz = PyTuple_Size(tmp);
ndim = lim_obj_sz;
if(PyErr_Occurred())
{
return -1;
}
if(lim_obj_sz < 1)
{
PyErr_SetString(PyExc_ValueError,
"Size limits cannot be empty");
}
if(pos_flag == RPN_IF_POSITION_XDIM)
{
PyObject *item = PyTuple_GET_ITEM(tmp, 0);
Py_ssize_t tmp = PyLong_AsSsize_t(item);
if(PyErr_Occurred())
{
PyErr_SetString(PyExc_ValueError,
"Unable to convert size_lim[0] to int");
return -1;
}
if(lim_obj_sz != tmp + 1)
{
PyErr_Format(PyExc_ValueError,
"Xdim indicate %d size_lim but len(size_lim)=%d",
tmp+1, lim_obj_sz);
return -1;
}
expt_sizes[0] = ndim = tmp;
}
else
{
if(lim_obj_sz != expt_sizes[0])
{
PyErr_Format(PyExc_ValueError,
"Expected %d size_lim but len(size_lim)=%d",
expt_sizes[0], lim_obj_sz);
return -1;
}
}
size_t sz_limits[lim_obj_sz];
for(Py_ssize_t i = 0; i<lim_obj_sz; i++)
{
PyObject *item = PyTuple_GET_ITEM(tmp, i);
sz_limits[i] = PyLong_AsSize_t(item);
if(PyErr_Occurred())
{
PyErr_Format(PyExc_ValueError,
"Unable to convert size_lim[%d] to unsigned int",
i);
return -1;
}
}
expr_self->ndim = ndim;
//Check & convert const values
Py_ssize_t values_obj_sz = 0;
tmp = NULL;
if(expt_sizes[1] > 0)
{
tmp = const_values_obj;
if(!PyTuple_Check(tmp))
{
PyErr_SetString(PyExc_ValueError,
"Invalid type for const_values argument");
return -1;
}
values_obj_sz = PyTuple_Size(tmp);
if(values_obj_sz != expt_sizes[1])
{
PyErr_Format(PyExc_ValueError,
"Expected %d const_values but len(const_values)=%d",
expt_sizes[1], values_obj_sz);
return -1;
}
}
rpn_value_t const_values[values_obj_sz];
for(Py_ssize_t i = 0; i<values_obj_sz; i++)
{
PyObject *item = PyTuple_GET_ITEM(tmp, i);
const_values[i] = PyLong_AsRpnValue_t(item);
if(PyErr_Occurred())
{
PyErr_Format(PyExc_ValueError,
"Unable to convert size_lim[%d] to unsigned int",
i);
return -1;
}
}
// Creating rif params
rpn_if_param_t *rif_params;
if(!(rif_params = rpn_if_default_params(pos_flag, res_flag,
sz_limits, const_values, stack_size)))
{
PyErr_SetString(PyExc_ValueError, "Unable to create parameters \
with given arguments");
return -1;
}
// Creating rif with a new memory map
expr_self->rif = rpn_if_new(rif_params, NULL);
return 0;
}
/**@brief Returns the parameters in a named tuple */
PyObject *rpnif_get_params(PyObject *self)
{
PyObject *res, *val;
rpn_if_default_data_t *params;
params = (rpn_if_default_data_t*)(((PyRPNIterExpr_t*)self)->rif->params->data);
short expt_sizes[2];
if(rpn_if_sizes_from_flag(params->pos_flag, params->res_flag, expt_sizes) < 0)
{
PyErr_SetString(PyExc_RuntimeError, "Invalid internal state");
return NULL;
}
res = PyStructSequence_New(&rpnif_params_SeqDesc);
if(!res)
{
return NULL;
}
val = PyLong_FromLong(params->pos_flag);
PyStructSequence_SET_ITEM(res, 0, val);
val = PyLong_FromLong(params->res_flag);
PyStructSequence_SET_ITEM(res, 1, val);
if(params->pos_flag == RPN_IF_POSITION_XDIM)
{
expt_sizes[0] = params->size_lim[0] + 1;
}
PyObject *lim = PyTuple_New(expt_sizes[0]);
if(!lim)
{
Py_DECREF(res);
return NULL;
}
PyStructSequence_SET_ITEM(res, 2, lim);
for(Py_ssize_t i=0; i<expt_sizes[0]; i++)
{
val = PyLong_FromSize_t(params->size_lim[i]);
PyTuple_SET_ITEM(lim, i, val);
}
if(!params->const_val)
{
Py_INCREF(Py_None);
PyTuple_SET_ITEM(res, 3, Py_None);
}
else
{
PyObject *values = PyTuple_New(expt_sizes[1]);
if(!values)
{
Py_DECREF(res);
return NULL;
}
PyStructSequence_SET_ITEM(res, 3, values);
for(Py_ssize_t i=0; i<expt_sizes[1]; i++)
{
val = PyLong_FromRpnValue_t(params->const_val[i]);
PyTuple_SET_ITEM(values, i, val);
}
}
return res;
}
void rpnif_del(PyObject *self)
{
PyRPNIterExpr_t *expr_self;
@ -125,26 +362,98 @@ void rpnif_del(PyObject *self)
}
}
int rpnif_getbuffer(PyObject *self, Py_buffer *view, int flags)
{
PyRPNIterExpr_t *expr_self;
expr_self = (PyRPNIterExpr_t*)self;
rpn_if_default_data_t *data = (rpn_if_default_data_t*)expr_self->rif->params->data;
view->buf = expr_self->rif->mem;
view->obj = self;
view->len = expr_self->rif->params->mem_sz * expr_self->rif->params->value_sz;
view->readonly = 0;
//view->itemsize = expr_self->rif->params->value_sz;
view->itemsize = sizeof(rpn_value_t);
if(flags & PyBUF_FORMAT)
{
view->format = "L";
}
else
{
view->format = NULL;
}
view->ndim = 1;
view->shape = NULL;
if(flags & PyBUF_ND)
{
view->ndim = expr_self->ndim;
// !! Error if value_sz < sizeof(rpn_value_t) !!
short nval = view->itemsize / sizeof(rpn_value_t);
if(nval > 1)
{
view->ndim++;
}
view->shape = malloc(sizeof(Py_ssize_t) * view->ndim); /**@todo check error*/
for(size_t i=0; i<expr_self->ndim; i++)
{
int idx = i;
if(data->pos_flag == RPN_IF_POSITION_XDIM)
{
idx++;
}
view->shape[i] = data->size_lim[idx];
}
if(nval>1)
{
view->shape[expr_self->ndim] = nval;
}
}
view->strides = NULL;
view->suboffsets = NULL;
Py_INCREF(self);
return 0;
}
void rpnif_releasebuffer(PyObject *self, Py_buffer *view)
{
free(view->shape);
}
PyObject* rpnif_str(PyObject *self)
{
PyErr_SetString(PyExc_NotImplementedError,
"Not implemented");
return NULL;
/**@todo TODO write the function */
Py_RETURN_NONE;
}
PyObject* rpnif_repr(PyObject *self)
{
PyErr_SetString(PyExc_NotImplementedError,
"Not implemented");
return NULL;
/**@todo TODO write the function */
Py_RETURN_NONE;
}
PyObject* rpnif_getstate(PyObject *self, PyObject *noargs)
{
PyErr_SetString(PyExc_NotImplementedError,
"Not implemented");
return NULL;
/**@todo TODO write the function */
Py_RETURN_NONE;
}
PyObject* rpnif_setstate(PyObject *self, PyObject *state_bytes)
{
PyErr_SetString(PyExc_NotImplementedError,
"Not implemented");
return NULL;
/**@todo TODO write the function */
Py_RETURN_NONE;
}

View file

@ -49,6 +49,10 @@
* This file is the header of the RPNIterExpr Python class
*/
/**@brief A single instance of this object is used for parameters representation */
extern PyTypeObject rpnif_params_SeqDesc;
extern PyStructSequence_Desc rpnif_params_desc;
/**@brief RPNIterExpr Python class methods list
* @ingroup python_if */
extern PyMethodDef RPNIterExpr_methods[];
@ -65,6 +69,9 @@ typedef struct
{
PyObject_VAR_HEAD;
/** @brief Number of dimention in map */
size_t ndim;
/**@brief Pointer on @ref rpn_if_s */
rpn_if_t *rif;
@ -93,6 +100,12 @@ int rpnif_init(PyObject *self, PyObject *args, PyObject *kwds);
*/
void rpnif_del(PyObject *self);
int rpnif_getbuffer(PyObject *self, Py_buffer *view, int flags);
void rpnif_releasebuffer(PyObject *self, Py_buffer *view);
PyObject *rpnif_get_params(PyObject *self);
/**@brief RPNIterExpr __getstate__ method for pickling
* @param cls RPNIterExpr type object
* @param noargs Not an argument...

View file

@ -100,6 +100,24 @@ PyInit_pyrpn(void)
return NULL;
}
// Named tuple for RPNIterExpr's params
PyStructSequence_InitType(&rpnif_params_SeqDesc,&rpnif_params_desc);
Py_INCREF(&rpnif_params_SeqDesc);
/*
PyObject_SetAttrString((PyObject*)rpnif_params_SeqDesc, "__module__",
mod);
*/
if(PyModule_AddObject(mod, "RPNIterExprParams",
(PyObject*)&rpnif_params_SeqDesc) < 0)
{
Py_DECREF(&RPNExprType);
Py_DECREF(&RPNIterExprType);
Py_DECREF(&rpnif_params_SeqDesc);
Py_DECREF(mod);
return NULL;
}
return mod;
}

114
tests/tests_rpniter.py Executable file
View file

@ -0,0 +1,114 @@
#!/usr/bin/python3
import unittest
import warnings
try:
import numpy as np
except (ImportError, NameError) as e:
np = None
warnings.warn("Numpy not found, some tests skipped", warnings.ImportWarning)
try:
import pyrpn
except (ImportError, NameError) as e:
print("Error importing pyrpn. Try to run make.",
file=sys.stderr)
raise e
def skipIfNoNumpy():
if np is not None:
return lambda func: func
return unittest.skip("Numpy not found")
class TestRPNIterInit(unittest.TestCase):
def test_init(self):
""" Test instanciation """
rif = pyrpn.RPNIterExpr(pyrpn.const.POS_XY,
pyrpn.const.RESULT_COUNT,
(640,480))
def test_params_simple(self):
""" Test parameters fetch from instanciation """
args = [(pyrpn.const.POS_XY,
pyrpn.const.RESULT_COUNT,
(640,480)),
(pyrpn.const.POS_LINEAR,
pyrpn.const.RESULT_BOOL,
(1024,))
]
for arg in args:
with self.subTest(args=arg):
rif = pyrpn.RPNIterExpr(*arg)
params = rif.get_params()
self.assertEqual(params.pos_flag, arg[0])
self.assertEqual(params.res_flag, arg[1])
self.assertEqual(params.size_lim, arg[2])
self.assertIsNone(params.const_values)
def test_params_const(self):
""" Test parameters from instanciation when const values used as result """
args = [(pyrpn.const.POS_XY,
pyrpn.const.RESULT_CONST,
(640,480),
(42,)),
(pyrpn.const.POS_LINEAR,
pyrpn.const.RESULT_CONST_RGBA,
(1024,), (13,37,13,12))
]
for arg in args:
with self.subTest(args=arg):
rif = pyrpn.RPNIterExpr(*arg)
params = rif.get_params()
self.assertEqual(params.pos_flag, arg[0])
self.assertEqual(params.res_flag, arg[1])
self.assertEqual(params.size_lim, arg[2])
self.assertEqual(params.const_values, arg[3])
def test_params_xdim(self):
""" Test parameters from instanciation when using xdim positionning """
args = [(pyrpn.const.POS_XDIM,
pyrpn.const.RESULT_BOOL,
(2,640,480)),
(pyrpn.const.POS_XDIM,
pyrpn.const.RESULT_BOOL,
(5,13,37,13,12,42)),
]
for arg in args:
with self.subTest(args=arg):
rif = pyrpn.RPNIterExpr(*arg)
params = rif.get_params()
self.assertEqual(params.pos_flag, arg[0])
self.assertEqual(params.res_flag, arg[1])
self.assertEqual(params.size_lim, arg[2])
self.assertIsNone(params.const_values)
@skipIfNoNumpy()
def test_buffer(self):
""" Test the buffer interface using numpy """
args = [((pyrpn.const.POS_XY,
pyrpn.const.RESULT_COUNT,
(640,480)), 640*480),
((pyrpn.const.POS_LINEAR,
pyrpn.const.RESULT_BOOL,
(1024,)), 1024),
((pyrpn.const.POS_XDIM,
pyrpn.const.RESULT_BOOL,
(2,640,480)), 640*480),
((pyrpn.const.POS_XDIM,
pyrpn.const.RESULT_BOOL,
(5,13,37,13,12,42)), 13*37*13*12*42),
]
for arg, expt_sz in args:
with self.subTest(args=arg):
rif = pyrpn.RPNIterExpr(*arg)
arr = np.frombuffer(rif, dtype=np.uint64)
self.assertEqual(len(arr), expt_sz)
arr += 0x11111111 # check writing to all bytes
if __name__ == '__main__':
unittest.main()