diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55b97b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.o +*.so +*.gcda +*.gcno +lcov.info +lcov_html diff --git a/Doxyfile.mk b/Doxyfile.mk index 790b866..2ae0d26 100644 --- a/Doxyfile.mk +++ b/Doxyfile.mk @@ -46,13 +46,13 @@ INLINE_GROUPED_CLASSES = NO INLINE_SIMPLE_STRUCTS = NO TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 -EXTRACT_ALL = YES -EXTRACT_PRIVATE = NO -EXTRACT_PACKAGE = NO -EXTRACT_STATIC = NO +EXTRACT_ALL = NO +EXTRACT_PRIVATE = YES +EXTRACT_PACKAGE = YES +EXTRACT_STATIC = YES EXTRACT_LOCAL_CLASSES = YES -EXTRACT_LOCAL_METHODS = NO -EXTRACT_ANON_NSPACES = NO +EXTRACT_LOCAL_METHODS = YES +EXTRACT_ANON_NSPACES = YES HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO HIDE_FRIEND_COMPOUNDS = NO @@ -83,7 +83,7 @@ QUIET = NO WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES -WARN_NO_PARAMDOC = NO +WARN_NO_PARAMDOC = YES WARN_AS_ERROR = NO WARN_FORMAT = "$file:$line: $text" INPUT = ./ @@ -151,7 +151,6 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES CLANG_ASSISTED_PARSING = NO ALPHABETICAL_INDEX = YES -COLS_IN_ALPHA_INDEX = 5 GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html @@ -199,14 +198,12 @@ PDF_HYPERLINKS = YES USE_PDFLATEX = YES LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO -LATEX_SOURCE_CODE = NO LATEX_BIB_STYLE = plain LATEX_TIMESTAMP = NO GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO -RTF_SOURCE_CODE = NO GENERATE_MAN = YES MAN_OUTPUT = man MAN_EXTENSION = .3 @@ -216,7 +213,6 @@ XML_OUTPUT = xml XML_PROGRAMLISTING = YES GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook -DOCBOOK_PROGRAMLISTING = NO GENERATE_AUTOGEN_DEF = NO GENERATE_PERLMOD = NO PERLMOD_LATEX = NO @@ -229,8 +225,6 @@ SKIP_FUNCTION_MACROS = YES ALLEXTERNALS = NO EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES -PERL_PATH = /usr/bin/perl -CLASS_DIAGRAMS = YES HIDE_UNDOC_RELATIONS = YES HAVE_DOT = YES DOT_NUM_THREADS = 0 diff --git a/Makefile b/Makefile index 3111325..65de272 100644 --- a/Makefile +++ b/Makefile @@ -3,13 +3,13 @@ NASM=nasm LD=ld ifeq ($(DEBUG), 1) - CFLAGS=-ggdb -fPIC -Wall -DDEBUG + CFLAGS=-ggdb -fPIC -Wall -DDEBUG -Wsign-compare LDFLAGS=-g NASMCFLAGS=-g -f elf64 #PYTHON=python3dm PYTHON=python3-dbg else - CFLAGS=-fPIC -Wall -Werror + CFLAGS=-fPIC -Wall -Werror -DNDEBUG LDFLAGS=-s NASMCFLAGS=-f elf64 PYTHON=python3 @@ -19,37 +19,40 @@ PYTHON_CONFIG=$(PYTHON)-config PYTHON_CFLAGS=`$(PYTHON_CONFIG) --includes` `$(PYTHON_CONFIG) --cflags` PYTHON_LDFLAGS=-shared -fPIC `$(PYTHON_CONFIG) --libs` `$(PYTHON_CONFIG) --ldflags|cut -d' ' -f1,2` +C_SOURCES=$(wildcard *.c) +C_OBJS=$(patsubst %.c,%.o,$(C_SOURCES)) +HEADERS=$(wildcard *.h) +ASM_SOURCES=$(wildcard *.asm) +ASM_OBJS=$(patsubst %.asm,%.o,$(ASM_SOURCES)) +OBJS=$(ASM_OBJS) $(C_OBJS) +LIB=pyrpn.so + +CFLAGS_COV=$(CFLAGS) --coverage +LDFLAGS_COV=$(LDFLAGS) +C_OBJS_COV=$(patsubst %.c,%_cov.o,$(C_SOURCES)) +C_GCNO=$(patsubst %.o,%.gcno,$(C_OBJS_COV)) +C_GCDA=$(patsubst %.o,%.gcda,$(C_OBJS_COV)) +OBJS_COV=$(ASM_OBJS) $(C_OBJS_COV) +LIB_COV=tests/pyrpn.so + all: .deps pyrpn.so -pyrpn.so: python_pyrpn.o python_rpnexpr.o rpn_lib.o rpn_jit.o rpn_parse.o rpn_mutation.o rpn_if.o rpn_if_default.o rpn_ifs.o +$(C_OBJS): %.o: %.c $(HEADERS) + $(CC) $(PYTHON_CFLAGS) $(CFLAGS) -c -o $@ $< + +$(ASM_OBJS): %.o: %.asm $(HEADERS) + $(NASM) $(NASMCFLAGS) -o $@ $< + +$(LIB): $(OBJS) $(LD) $(LDFLAGS) $(PYTHON_LDFLAGS) -o $@ $^ -python_pyrpn.o: python_pyrpn.c python_rpnexpr.h python_rpnexpr.o rpn_jit.o - $(CC) $(PYTHON_CFLAGS) $(CFLAGS) -c $< -python_rpnexpr.o: python_rpnexpr.c python_rpnexpr.h rpn_jit.o - $(CC) $(PYTHON_CFLAGS) $(CFLAGS) -c $< +$(C_OBJS_COV): %_cov.o: %.c $(HEADERS) + $(CC) $(PYTHON_CFLAGS) $(CFLAGS_COV) -c -o $@ $< -rpn_jit.o: rpn_jit.c rpn_jit.h rpn_parse.o rpn_lib.o - $(CC) $(CFLAGS) -c $< +$(LIB_COV): $(OBJS_COV) + gcc $(LDFLAGS_COV) $(PYTHON_LDFLAGS) -o $@ $^ --coverage -lgcov -rpn_parse.o: rpn_parse.c rpn_parse.h rpn_lib.o - $(CC) $(CFLAGS) -c $< - -rpn_mutation.o: rpn_mutation.c rpn_mutation.h rpn_parse.o - $(CC) $(CFLAGS) -c $< - -rpn_if.o: rpn_if.c rpn_if.h rpn_jit.o - $(CC) $(CFLAGS) -c $< - -rpn_if_default.o: rpn_if_default.c rpn_if_default.h rpn_if.o - $(CC) $(CFLAGS) -c $< - -rpn_ifs.o: rpn_ifs.c rpn_ifs.h rpn_if.o - $(CC) $(CFLAGS) -c $< - -rpn_lib.o: rpn_lib.asm rpn_lib.h - $(NASM) $(NASMCFLAGS) -o $@ $< # Doxygen documentation doc: doc/.doxygen.stamp @@ -69,20 +72,37 @@ doc/.doxygen.stamp: $(wildcard *.c) $(wildcard *.h) Doxyfile .PHONY: clean distclean checks runtest unittest benchmark -checks: runtest unittest benchmark +checks: runtest unittest -benchmark: pyrpn.so - PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py 0x200 0x3000 +benchmark: $(LIB) + PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py -c 0x500 -i 0x2000 -s 0x50; \ + PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py -c 0x500 -i 0x2000 -s 0xa0; \ + PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py -c 0x500 -i 0x2000 -s 0x100; \ + PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py -c 0x500 -i 0x5000 -s 0x50; \ + PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py -c 0x500 -i 0x5000 -s 0xa0; \ + PYTHONPATH=`pwd` $(PYTHON) tests/benchmark.py -c 0x500 -i 0x5000 -s 0x100; \ + + +unittest: $(LIB_COV) + cd tests;\ + $(PYTHON) -m unittest -v -f + +lcov.info: unittest runtest + lcov --no-external --base-directory ./ --capture --directory ./ --output-file $@ + +lcov_html: lcov.info + genhtml $< --output-directory $@ + +coverage: lcov_html -unittest: pyrpn.so - PYTHONPATH=`pwd` $(PYTHON) -m unittest -v runtest: make -C tests clean: - -rm -fv *.o pyrpn.so test;\ + -rm -fv $(OBJS) $(LIB) test;\ rm -fRv doc/.doxygen.stamp doc/* Doxyfile;\ + rm -fRv $(OBJS_COV) $(C_GCNO) $(C_GCDA) $(LIB_COV) lcov_html lcov.info;\ make -C tests clean distclean: clean diff --git a/benchplot.sh b/benchplot.sh new file mode 100755 index 0000000..495697d --- /dev/null +++ b/benchplot.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +gpdat=/tmp/bench.dat + +e=500 + +min_i=10000 +max_i=100000 +it_i=1 + +min_s=10 +max_s=10000 +it_s=30 + +if [ $it_i -eq 1 ] +then + inc_i=$max_i +else + inc_i=$(expr $(expr $max_i - $min_i) / $(expr $it_i - 1)) +fi +inc_s=$(expr $(expr $max_s - $min_s) / $(expr $it_s - 1)) + + +for i in $(seq $min_i $inc_i $max_i) +do + for s in $(seq $min_s $inc_s $max_s) + do + echo "GO $e $i $s" + PYTHONPATH=./ python3 tests/benchmark.py -c $e -i $i -s $s -G $gpdat + done + echo "" >> $gpdat +done diff --git a/config.h b/config.h index 3fca398..994dd9d 100644 --- a/config.h +++ b/config.h @@ -18,5 +18,24 @@ */ #ifndef __RPN_CONFIG__ #define __RPN_CONFIG__ + +/**@file config.h + * @brief Global definition for all C files + */ + +/**@brief The programms is under GPL */ #define _GNU_SOURCE + +/**@brief Allow fancy method declaration by indicating arguments list + * explicitly + * @param name char* The method name + * @param callback The C function to call + * @param flags int Python flags for method call options + * @param header char* List of arguments + * @param docstring char* The method documentation + */ +#define PYRPN_method(name, callback, flags, header, docstring) \ +{name, (PyCFunction)callback, flags, \ + PyDoc_STR(name "("header ")\n--\n\n" docstring)} + #endif diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..d4a3749 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,4 @@ +#!/bin/sh +echo apt install htop vim git python3 python3-pip python3-numpy python3-sklearn screen nasm python3-tqdm python3-skimage +echo pip3 install alphashape +echo make diff --git a/python_const.c b/python_const.c new file mode 100644 index 0000000..d6ddf54 --- /dev/null +++ b/python_const.c @@ -0,0 +1,168 @@ +#include "python_const.h" +/**@file python_const.c + * @brief pyrpn.const code + * @ingroup python_ext + * @ingroup pymod_pyrpn_RPNIterParams + * @ingroup pymod_pyrpn_RPNTokenTypes + * @ingroup pymod_pyrpn_RPNMutationParams + */ + +PyModuleDef rpnconstmodule = { + PyModuleDef_HEAD_INIT, + "pyrpn.const", + "librarie's constants", + -1, // module size + NULL, // methods + NULL, + NULL, + NULL +}; + +/* Various named tuple definition, instanciated in python_rpnexpr module init */ +/**@brief @ref pymod_pyrpn_RPNIterParams fields definition + * @ingroup pymod_pyrpn_RPNIterParams */ +static PyStructSequence_Field params_fields[] = { + { + .name = "argc", + .doc = "Number of arguments expected by expressions", + }, + { .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", + }, + { + .name = "memory_size", + .doc = "The size of the underlying memory map data are \ +readed/written to", + }, + { 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; + +/**@brief @ref pymod_pyrpn_RPNTokenTypes field definition + * @ingroup pymod_pyrpn_RPNTokenTypes */ +static PyStructSequence_Field token_types_fields[] = { + { .name = "op", + .doc = "Operation", + }, + { .name = "const", + .doc = "Constant value", + }, + { .name = "var", + .doc = "Variable (argument)", + }, + { NULL, NULL }, +}; +PyStructSequence_Desc rpn_token_types_desc = { + .name = "RPNTokenTypes", + .doc = "Named tuple with token types as keys", + .n_in_sequence = 3, + .fields = token_types_fields, +}; +PyTypeObject rpn_token_types_SeqDesc; + + +/**@brief Adds a constant to @ref pymod_pyrpn_const module + * @param mod The @ref pymod_pyrpn_const module + * @param name The constant's name + * @param value The constant's value + * @return 0 or -1 on error + */ +int Py_rpnconst_add(PyObject* mod, const char* name, int value) +{ + PyObject *val; + val = Py_BuildValue("i", value); + Py_INCREF(val); + if(PyModule_AddObject(mod, name, val) < 0) + { + Py_DECREF(val); + return -1; + } + return 0; +} + +/**@brief Module initialisation function + * @return The initialized module */ +PyObject *rpnconst_init(void) +{ + PyObject *mod; + mod = PyModule_Create(&rpnconstmodule); + if(mod == NULL) { return NULL; } + + if(Py_rpnconst_add(mod, "POS_LINEAR", RPN_IF_POSITION_LINEAR) || + Py_rpnconst_add(mod, "POS_XY", RPN_IF_POSITION_XY) || + Py_rpnconst_add(mod, "POS_XDIM", RPN_IF_POSITION_XDIM) || + Py_rpnconst_add(mod, "RESULT_BOOL", RPN_IF_RES_BOOL) || + Py_rpnconst_add(mod, "RESULT_CONST", RPN_IF_RES_CONST) || + Py_rpnconst_add(mod, "RESULT_CONST_RGBA", RPN_IF_RES_CONST_RGBA) || + Py_rpnconst_add(mod, "RESULT_COUNT", RPN_IF_RES_COUNT) || + Py_rpnconst_add(mod, "RESULT_XFUN", RPN_IF_RES_XFUN) || + Py_rpnconst_add(mod, "RESULT_RGB", RPN_IF_RES_RGB) || + Py_rpnconst_add(mod, "RESULT_RGBA", RPN_IF_RES_RGBA)) + { + Py_DECREF(mod); + return NULL; + } + return mod; +} + +PyObject *pyrpn_PyErr_ReraiseFormat(PyObject *exception, const char *fmt, ...) +{ + va_list vargs; + PyObject *exc, *val, *val2, *tb; + + va_start(vargs, fmt); + + // we ay trigger a warning when in debug mode ? +#ifdef NDEBUG + assert(PyErr_Occurred()); +#else + if(!PyErr_Occurred()) + { + PyErr_WarnEx(PyExc_SyntaxWarning, + "Warning, reraising but PyErr_Occurred() not set", 1); + PyErr_FormatV(exception, fmt, vargs); + va_end(vargs); + return NULL; + } +#endif + PyErr_Fetch(&exc, &val, &tb); + PyErr_NormalizeException(&exc, &val, &tb); + if(tb != NULL) + { + PyException_SetTraceback(val, tb); + Py_DECREF(tb); + } + Py_DECREF(exc); + assert(!PyErr_Occurred()); + + PyErr_FormatV(exception, fmt, vargs); + + PyErr_Fetch(&exc, &val2, &tb); + PyErr_NormalizeException(&exc, &val2, &tb); + Py_INCREF(val); + PyException_SetCause(val2, val); + PyException_SetContext(val2, val); + PyErr_Restore(exc, val2, tb); + + va_end(vargs); + return NULL; +} + diff --git a/python_const.h b/python_const.h new file mode 100644 index 0000000..1a593ee --- /dev/null +++ b/python_const.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 Weber Yann + * + * This file is part of pyrpn. + * + * pyrpn is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * pyrpn is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with pyrpn. If not, see . + */ +#ifndef _PYTHON_CONST_H__ +#define _PYTHON_CONST_H__ + +#include "config.h" + +#include +#include + +#define PY_SSIZE_T_CLEAN +#include +#include "structmember.h" + +#include "rpn_mutate.h" +#include "rpn_if_mutate.h" +#include "rpn_ifs_mutate.h" +#include "rpn_if_default.h" + +/**@file python_const.h + * @brief Python pyrpn.const module headers + * @ingroup python_ext + * @ingroup pymod_pyrpn_const + */ + +/**@defgroup pymod_pyrpn_const module pyrpn.const + * @brief A module containing constant values + * + * Constants values are @ref ifs_if_default_posflag + * and @ref ifs_if_default_resflag + * @ingroup pymod_pyrpn + */ + +/**@brief pyrpn.const module specs + * @ingroup pymod_pyrpn */ +extern PyModuleDef rpnconstmodule; + +/**@defgroup pymod_pyrpn_RPNIterParams pyrpn.RPNIterParams + * @brief namedtuple representing @ref pymod_pyrpn_RPNExprIter parameters + * @ingroup pymod_pyrpn */ +/**@brief RPNIterParams named tuple for RPNIterExpr parameters + * @ingroup pymod_pyrpn_RPNIterParams */ +extern PyTypeObject rpnif_params_SeqDesc; +/**@brief @ref rpnif_params_SeqDesc named tuple description + * @ingroup pymod_pyrpn_RPNIterParams */ +extern PyStructSequence_Desc rpnif_params_desc; + +/**@defgroup pymod_pyrpn_RPNTokenTypes pyrpn.RPNTokenTypes + * @brief namedtuple with @ref pymod_pyrpn_token_Token subclass + * @ingroup pymod_pyrpn */ +/**@brief RPNTokenTypes named tuple with token types */ +extern PyTypeObject rpn_token_types_SeqDesc; +/**@brief @ref rpn_token_types_SeqDesc named tuple description */ +extern PyStructSequence_Desc rpn_token_types_desc; + +/**@brief pyrpn.const module initialisation function + * @return The initialized module + * @ingroup pymod_pyrpn */ +PyObject *rpnconst_init(void); + +PyObject *pyrpn_PyErr_ReraiseFormat(PyObject *exception, const char *fmt, ...); + +#endif diff --git a/python_if.c b/python_if.c index 5cd357a..5ecf377 100644 --- a/python_if.c +++ b/python_if.c @@ -1,8 +1,1853 @@ +/* + * Copyright (C) 2020,2023 Weber Yann + * + * This file is part of pyrpn. + * + * pyrpn is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * pyrpn is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with pyrpn. If not, see . + */ #include "python_if.h" +/**@file python_if.c + * @brief Python RPNIterExpr type definition + * @ingroup python_ext + * @ingroup pymod_pyrpn_RPNExprIter + */ -int rpnexpr_init(PyObject *self, PyObject *args, PyObject *kwds) +/**@brief @ref pymod_pyrpn_RPNExprIter methods definition + * @ingroup pymod_pyrpn_RPNExprIter */ +static PyMethodDef RPNIterExpr_methods[] = { + PYRPN_method("params", rpnif_params, + METH_VARARGS | METH_KEYWORDS | METH_CLASS, + /**@todo add default values */ + "cls, pos_flag, res_flag, size_lim, const_values, stack_size", + "Get a name tuple with parameters given a position \ +flag, a result flag and size limits"), + PYRPN_method("get_params", rpnif_get_params, + METH_NOARGS, + "self, /", + "Get a name tuple with parameters"), + PYRPN_method("set_mmap", rpnif_set_mmap, + METH_O, + "self, mmap, /", + "Set the mmap that stores data"), + PYRPN_method("shape", rpnif_shape, + METH_NOARGS, + "self, /", + "Get the shape of the data buffer"), + PYRPN_method("keys", rpnif_keys, + METH_NOARGS, + "self, /", + "Return items keys (see dict.keys)"), + PYRPN_method("values", rpnif_values, + METH_NOARGS, + "self, /", + "Return items values (see dict.values)"), + PYRPN_method("items", rpnif_items, + METH_NOARGS, + "self, /", + "Return items (key, value) list (see dict.items)"), + PYRPN_method("step", rpnif_step, + METH_O, + "self, position, /", + "Run an IF given a position and return a new position"), + PYRPN_method("mutate", rpnif_mutate, + METH_VARARGS | METH_KEYWORDS, + "self, n_mutations=1, params=None", + "Mutate the expressions in the IF"), + PYRPN_method("to_pos", rpnif_to_pos, + METH_FASTCALL, + "self, *args, /", + "Return a position (int) from a coordinates given as" + "argument."), + PYRPN_method("from_pos", rpnif_from_pos, + METH_O, + "self, position, /", + "Return a coordinates tuple from given position."), + PYRPN_method("__getstate__", rpnif_getstate, + METH_NOARGS, + "self, /", + "Pickling method (see pickle module).\n" + "Return a bytes representation of the expression state."), + PYRPN_method("__setstate__", rpnif_setstate, + METH_O, + "self, state, /", + "Unpickling method (see pickle module)."), + {NULL} //Sentinel +}; + +/**@brief @ref pymod_pyrpn_RPNExprIter members definition + * @ingroup pymod_pyrpn_RPNExprIter */ +static PyMemberDef RPNIterExpr_members[] = { + {"expressions", T_OBJECT, offsetof(PyRPNIterExpr_t, expr), READONLY, + "The tuple of expressions"}, + {"mmap", T_OBJECT, offsetof(PyRPNIterExpr_t, mmap), READONLY, + "The mmap storing data"}, + {NULL} +}; + +/**@brief @ref pymod_pyrpn_RPNExprIter sequence methods definition + * @ingroup pymod_pyrpn_RPNExprIter */ +static PySequenceMethods RPNIterExpr_seq_methods = { + .sq_length = rpnif_len, + .sq_item = rpnif_expr_item, + .sq_ass_item = rpnif_expr_ass_item, +}; + +/**@brief @ref pymod_pyrpn_RPNExprIter mapping methods definition + * @ingroup pymod_pyrpn_RPNExprIter */ +static PyMappingMethods RPNIterExpr_mapping_methods = { + .mp_length = rpnif_len, + .mp_subscript = rpnif_subscript, + .mp_ass_subscript = rpnif_ass_subscript, +}; + +/**@brief @ref pymod_pyrpn_RPNExprIter attributes definition + * @ingroup pymod_pyrpn_RPNExprIter */ +static PyGetSetDef RPNIterExpr_getset[] = { + {NULL} +}; + +/**@brief @ref pymod_pyrpn_RPNExprIter buffer methods definition + * @ingroup pymod_pyrpn_RPNExprIter */ +static PyBufferProcs RPNIterExpr_as_buffer = { + (getbufferproc)rpnif_getbuffer, + (releasebufferproc)rpnif_releasebuffer, +}; + +PyTypeObject RPNIterExprType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "pyrpn.RPNIterExpr", + .tp_basicsize = sizeof(PyRPNIterExpr_t), + .tp_itemsize = 0, + .tp_del = rpnif_del, + .tp_repr = rpnif_repr, + .tp_as_sequence = &RPNIterExpr_seq_methods, + .tp_as_mapping = &RPNIterExpr_mapping_methods, + .tp_str = rpnif_str, + .tp_as_buffer = &RPNIterExpr_as_buffer, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = "RPN expression evaluator", + .tp_methods = RPNIterExpr_methods, + .tp_members = RPNIterExpr_members, + .tp_getset = RPNIterExpr_getset, + .tp_init = rpnif_init, + .tp_new = rpnif_new, +}; + +PyObject* rpnif_params(PyObject *cls, PyObject *args, PyObject *kwds) { - /**@todo Need a C function returning RPNExpr (or must take - RPNExpr as arguments... */ + char *names[] = {"pos_flag", "res_flag", "size_lim", "const_values", "stack_size", NULL}; + + unsigned short pos_flag, res_flag, stack_size; + PyObject *lim_obj, *const_values_obj; + + stack_size = 16; + const_values_obj = lim_obj = NULL; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, + "HHO|OH:RPNIterExppr.get_params", names, + &pos_flag, &res_flag, &lim_obj, &const_values_obj, + &stack_size)) + { + return NULL; + } + + rpn_if_param_t *rif_params = _rpnif_get_params(pos_flag, res_flag, + lim_obj, const_values_obj, stack_size); + if(!rif_params) + { + return NULL; + } + + PyObject *res = _rpnif_params_to_tuple(rif_params); + if(!res) + { + goto err; + } + free(rif_params); + return res; + +err: + free(rif_params); + return NULL; } +PyObject* rpnif_new(PyTypeObject *subtype, PyObject *args, PyObject* kwds) +{ + PyObject *ret, *err; + PyRPNIterExpr_t *expr; + ret = PyType_GenericNew(subtype, args, kwds); + if((err = PyErr_Occurred())) + { + Py_DECREF(err); + return ret; + } + expr = (PyRPNIterExpr_t*)ret; + expr->rif = NULL; + expr->rif_params = NULL; + expr->borrowed_if = 0; + + return ret; +} + + +int rpnif_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + PyRPNIterExpr_t *expr_self; + char *names[] = {"pos_flag", "res_flag", "size_lim", "const_values", "stack_size", "mmap", NULL}; + unsigned short pos_flag, res_flag, stack_size; + PyObject *lim_obj, *const_values_obj, *mmap_obj; + + + expr_self = (PyRPNIterExpr_t*)self; + + stack_size = 16; + const_values_obj = lim_obj = mmap_obj = NULL; + expr_self->rif = NULL; + expr_self->mmap = NULL; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, "HHO|OHO:RPNIterExpr.__init__", names, + &pos_flag, &res_flag, &lim_obj, &const_values_obj, &stack_size, + &mmap_obj)) + { + return -1; + } + + rpn_if_param_t *rif_params = _rpnif_get_params(pos_flag, res_flag, + lim_obj, const_values_obj, stack_size); + expr_self->rif_params = rif_params; + + if(!rif_params) + { + return -1; + } + + expr_self->ndim = ((rpn_if_default_data_t*)(rif_params->data))->ndim; + + const Py_ssize_t expt_sz = rif_params->mem_sz * rif_params->value_sz; + if(mmap_obj == Py_None) + { + Py_DECREF(mmap_obj); + mmap_obj = NULL; + } + if(!mmap_obj) + { + PyObject *fileno = PyLong_FromLong(-1); + PyObject *length = PyLong_FromSize_t(expt_sz); + mmap_obj = PyObject_CallFunctionObjArgs(mmap_cls, fileno, length, NULL); + Py_DECREF(fileno); + Py_DECREF(length); + if(PyErr_Occurred()) + { + return -1; + } + } + else + { + if(!PyObject_TypeCheck(mmap_obj, (PyTypeObject*)mmap_cls)) + { + PyErr_SetString(PyExc_TypeError, + "The mmap argument MUST be an instance \ +of mmap.mmap"); + return -1; + } + /**@todo check if mmap is shared & writable ? */ + if(PyObject_Length(mmap_obj) != (Py_ssize_t) expt_sz) + { + PyErr_Format(PyExc_ValueError, + "Expected mmap length is %ld but mmap with length %ld provided", + rif_params->mem_sz, PyObject_Length(mmap_obj)); + return -1; + } + } + + + if(PyObject_GetBuffer(mmap_obj, &expr_self->mm_buff, + PyBUF_CONTIG) == -1) + { + return -1; + } + + expr_self->mmap = mmap_obj; + expr_self->_mmap = expr_self->mm_buff.buf; + + // Creating rif with a new memory map + expr_self->rif = rpn_if_new(rif_params, expr_self->mm_buff.buf, NULL); + if(!expr_self->rif) + { + PyErr_Format(PyExc_RuntimeError, + "Error initalizing if : %s", strerror(errno)); + return -1; + } + + // Creating the tuple holding RPNExpr instances of expressions + return _rpnif_init_expr_tuple(expr_self); +} + + +PyObject* rpnif_init_borrowed(rpn_if_t *borrowed_if, PyObject *mmap_obj) +{ + PyObject *sz_lim, *const_val; + PyObject *args, *ret; + + + rpn_if_default_data_t *params; + params = (rpn_if_default_data_t*)(borrowed_if->params->data); + + const size_t nsz = params->ndim + (params->pos_flag == RPN_IF_POSITION_XDIM?1:0); + sz_lim = PyTuple_New(nsz); + for(size_t i=0; isize_lim[i]); + PyTuple_SET_ITEM(sz_lim, i, v); + } + + const size_t const_val_sz = params->res_flag == RPN_IF_RES_CONST ? 1 : \ + (params->res_flag == RPN_IF_RES_CONST_RGBA ? 4 : 0); + + if(const_val_sz) + { + const_val = PyTuple_New(const_val_sz); + for(size_t i=0; iconst_val[i]); + PyTuple_SET_ITEM(const_val, i, v); + } + } + else + { + const_val = Py_None; + Py_INCREF(const_val); + } + + // no mmap :/ + // It seems there is no elegant solutions for the moment + // if we give None a new mmap is created + // if we give a dummy mmap it has to have the expected size... + // using None for the moment... + /**@todo find a solution */ + PyObject *mmap_arg = Py_None; + if(mmap_obj) + { + mmap_arg = mmap_obj; + } + + args = Py_BuildValue("hhOOBO", params->pos_flag, params->res_flag, + sz_lim, const_val, + borrowed_if->params->rpn_stack_sz, + mmap_arg); + Py_DECREF(sz_lim); + Py_DECREF(const_val); + if(!args || PyErr_Occurred()) + { + return NULL; + } + + ret = PyObject_CallObject((PyObject*)&RPNIterExprType, args); + Py_DECREF(args); + if(!ret || PyErr_Occurred()) + { + return NULL; + } + + PyRPNIterExpr_t* instance = (PyRPNIterExpr_t*)ret; + + // changing if instance + rpn_if_free(instance->rif); + instance->rif = borrowed_if; + // reseting expr tuple + Py_DECREF(instance->expr); + instance->expr = NULL; + + // mmap update + PyBuffer_Release(&instance->mm_buff); + if(!mmap_obj) + { + Py_DECREF(instance->mmap); + instance->mmap = NULL; + instance->mm_buff.buf = NULL; + instance->_mmap = borrowed_if->mem; + } + instance->borrowed_if = 1; + // someone else will take care to free the rif params + if(instance->rif_params) { free(instance->rif_params); } + instance->rif_params = NULL; + + if(_rpnif_init_expr_tuple(instance) < 0) + { + return NULL; + } + return ret; +} + +/**@brief Returns the parameters in a named tuple */ +PyObject *rpnif_get_params(PyObject *self) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + return _rpnif_params_to_tuple(expr_self->rif->params); +} + +PyObject *rpnif_set_mmap(PyObject *self, PyObject *mm_obj) +{ + if(!PyObject_TypeCheck(mm_obj, (PyTypeObject*)mmap_cls)) + { + PyErr_Format(PyExc_TypeError, + "Excpected instance of mmap.mmap"); + return NULL; + } + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + + Py_buffer buff; + + /**@todo check mm size &writable ? */ + if(PyObject_GetBuffer(mm_obj, &buff, PyBUF_CONTIG) == -1) + { + return NULL; + } + + // Release old mmap + if(expr_self->mmap) + { + PyBuffer_Release(&expr_self->mm_buff); + Py_DECREF(expr_self->mmap); + } + + if(expr_self->rif->self_mem) // should never be true ? + { + const size_t mm_sz = expr_self->rif->params->mem_sz *\ + expr_self->rif->params->value_sz; + munmap(expr_self->rif->mem, mm_sz); + } + + // Set new mmap in python struct & rif struct + expr_self->mm_buff = buff; + expr_self->mmap = mm_obj; + expr_self->_mmap = buff.buf; + + expr_self->rif->mem = buff.buf; + expr_self->rif->self_mem = 0; + + Py_RETURN_NONE; +} + +PyObject *rpnif_shape(PyObject *self) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + rpn_if_default_data_t *params; + params = (rpn_if_default_data_t*)(expr_self->rif->params->data); + + size_t res_sz; + + switch(params->res_flag) + { + case RPN_IF_RES_RGB: + res_sz = 3; + break; + case RPN_IF_RES_RGBA: + res_sz = 4; + break; + default: + res_sz = 1; + break; + } + + const size_t shape_sz = params->ndim + (res_sz > 1 ?1:0); + + PyObject *ret = PyTuple_New(shape_sz); + if(!ret) + { + return NULL; + } + + size_t i; + for(i = 0; i < params->ndim; i++) + { + size_t idx = params->pos_flag == RPN_IF_POSITION_XDIM ? i+1:i; + PyObject *d = PyLong_FromLong(params->size_lim[idx]); + if(!d) + { + goto item_err; + } + PyTuple_SET_ITEM(ret, i, d); + } + if(res_sz > 1) + { + PyObject *d = PyLong_FromLong(res_sz); + if(!d) + { + goto item_err; + } + PyTuple_SET_ITEM(ret, i, d); + } + return ret; + +item_err: + Py_DECREF(ret); + return NULL; +} + + +PyObject *rpnif_step(PyObject *self, PyObject* opos) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + /** @todo allow tuple as argument !!!!! */ + if(!PyLong_Check(opos)) + { + PyErr_SetString(PyExc_TypeError, "Expected position to be an int"); + return NULL; + } + size_t pos = PyLong_AsSize_t(opos); + if(PyErr_Occurred()) + { + return NULL; + } + + pos = rpn_if_step(expr_self->rif, pos); + + return PyLong_FromSize_t(pos); +} + + +PyObject* rpnif_mutate(PyObject *self, PyObject *args, PyObject *kwargs) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + size_t rpn_sz = expr_self->rif->params->rpn_sz; + + char *str_args = "|IOO:RPNExpr.mutate"; + char *names[] = {"n_mutations", "params", "weights", NULL}; + + PyObject *py_params = NULL, *py_weights = NULL; + unsigned int n_mutations = 1; + rpn_mutation_params_t params; + rnd_t *weights; + if(!if_mutation_alloc_weights(expr_self->rif, &weights)) + { + PyErr_Format(PyExc_MemoryError, + "Unable to allocate weights : %s\n", + strerror(errno)); + return NULL; + } + + if(!PyArg_ParseTupleAndKeywords(args, kwargs, str_args, names, + &n_mutations, &py_params, &py_weights)) + { + goto err; + } + + if(!py_weights || py_weights == Py_None) + { + for(size_t i=0; irif, &fweights)) + { + PyErr_Format(PyExc_MemoryError, + "Unable to allocate memory : %s", + strerror(errno)); + goto err; + } + for(size_t i=0; irif, weights, n_mutations, ¶ms) < 0) + { + free(weights); + PyErr_Format(PyExc_RuntimeError, + "Error during mutations : %s", + strerror(errno)); + goto err; + } + + free(weights); + Py_RETURN_NONE; + +err: + free(weights); + return NULL; +} + + +PyObject *rpnif_to_pos(PyObject *self, PyObject** argv, Py_ssize_t argc) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + rpn_if_default_data_t *rif_data = (rpn_if_default_data_t*)expr_self->rif->params->data; + return _rpnif_to_pos(rif_data, argv, argc); +} + + +PyObject *rpnif_from_pos(PyObject *self, PyObject* _pos) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + rpn_if_default_data_t *rif_data = (rpn_if_default_data_t*)expr_self->rif->params->data; + + return _rpnif_from_pos(rif_data, _pos); +} + + +PyObject *rpnif_keys(PyObject *self) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + rpn_if_default_data_t *rif_data = (rpn_if_default_data_t*)expr_self->rif->params->data; + + Py_ssize_t ret_idx = 0; + char xdim_key[64]; + + PyObject *ret = PyTuple_New(expr_self->rif->params->rpn_sz); + if(PyErr_Occurred()) + { + return NULL; + } + +#define _ret_append(key) PyTuple_SET_ITEM(ret, ret_idx++, \ + PyUnicode_FromString(key)) + + switch(rif_data->pos_flag) + { + case RPN_IF_POSITION_XY: + _ret_append("X"); + _ret_append("Y"); + break; + case RPN_IF_POSITION_LINEAR: + _ret_append("X"); + break; + case RPN_IF_POSITION_XDIM: + for(size_t i=0; isize_lim[0];i++) + { + snprintf(xdim_key, 64, "D%ld", i); + _ret_append(xdim_key); + } + break; + default: + PyErr_SetString(PyExc_RuntimeError, + "UNKOWN POS_FLAG3"); + return NULL; + } + switch(rif_data->res_flag) + { + case RPN_IF_RES_BOOL: + case RPN_IF_RES_XFUN: + _ret_append("R"); + break; + case RPN_IF_RES_RGB: + _ret_append("R"); + _ret_append("G"); + _ret_append("B"); + break; + case RPN_IF_RES_RGBA: + _ret_append("R"); + _ret_append("G"); + _ret_append("B"); + _ret_append("A"); + break; + default: + break; + } + + if(PyErr_Occurred()) + { + return NULL; + } + + return ret; + +#undef _ret_append +} + + +PyObject *rpnif_values(PyObject *self) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + Py_INCREF(expr_self->expr); + return expr_self->expr; +} + + +PyObject *rpnif_items(PyObject *self) +{ + size_t i; + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + PyObject *ret = PyTuple_New(expr_self->rif->params->rpn_sz); + if(PyErr_Occurred()) + { + return NULL; + } + PyObject *keys = rpnif_keys(self); + for(i=0; irif->params->rpn_sz; i++) + { + PyObject *tmp = PyTuple_New(2); + if(PyErr_Occurred()) + { + goto err_loop_newtuple; + } + PyObject *key = PyTuple_GET_ITEM(keys, i); + PyObject *value = PyTuple_GET_ITEM(expr_self->expr, i); + Py_INCREF(key); + Py_INCREF(value); + PyTuple_SET_ITEM(tmp, 0, key); + PyTuple_SET_ITEM(tmp, 1, value); + PyTuple_SET_ITEM(ret, i, tmp); + if(PyErr_Occurred()) + { + goto err_loop_setitem; + } + + + } + + return ret; + + /** @todo cleanup seems wrong... */ + for(i=i; i>=0; i--) + { +err_loop_setitem: + PyObject *key = PyTuple_GET_ITEM(keys, i); + PyObject *value = PyTuple_GET_ITEM(expr_self->expr, i); + Py_DECREF(key); + Py_DECREF(value); +err_loop_newtuple: + } + Py_DECREF(ret); + return NULL; +} + + +void rpnif_del(PyObject *self) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + if((!expr_self->borrowed_if) && expr_self->rif) + { + rpn_if_free(expr_self->rif); + expr_self->rif = NULL; + } + if(expr_self->mmap) + { + if(expr_self->mm_buff.buf) + { + PyBuffer_Release(&expr_self->mm_buff); + expr_self->mm_buff.buf = NULL; + } + Py_DECREF(expr_self->mmap); + expr_self->mmap = NULL; + } + if(expr_self->expr) + { + Py_DECREF(expr_self->expr); + expr_self->expr = NULL; + } + if(expr_self->rif_params) { free(expr_self->rif_params); } +} + + +Py_ssize_t rpnif_len(PyObject *self) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + return expr_self->rif->params->rpn_sz; +} + +PyObject* rpnif_expr_item(PyObject *self, Py_ssize_t idx) +{ + Py_ssize_t _idx = idx; + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + + if(idx < 0) + { + idx = expr_self->rif->params->rpn_sz - 1 + idx; + } + if(idx < 0 || (size_t) idx >= expr_self->rif->params->rpn_sz) + { + PyErr_Format(PyExc_IndexError, + "No expression %ld with given options", + _idx); + return NULL; + } + PyObject *ret = PyTuple_GET_ITEM(expr_self->expr, idx); + Py_INCREF(ret); + return ret; +} + + +int rpnif_expr_ass_item(PyObject *self, Py_ssize_t idx, PyObject* elt) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + + if(!PyUnicode_Check(elt)) + { + PyErr_SetString(PyExc_TypeError, "RPNExpr expected"); + return -1; + } + + PyObject *bytes_str = PyUnicode_AsASCIIString(elt); + if(PyErr_Occurred()) + { + return -1; + } + const char *code = PyBytes_AS_STRING(bytes_str); + + rpn_expr_t *expr = &(expr_self->rif->rpn[idx]); + + if(rpn_expr_recompile(expr, code) < 0) + { + PyErr_Format(PyExc_ValueError, + "Error during expression '%s' compilation : %s", + code, expr->err_reason); + return -1; + } + + return 0; +} + +/** Convert key to integer index + * @param self @ref PyRPNIterExpr_t instance + * @param _key An expression name (str) + * @return The expression index or -1 on error (raise python exception) + */ +static Py_ssize_t _rpnif_subscript_idx(PyObject *self, PyObject *_key) +{ + if(!PyUnicode_Check(_key)) + { + PyErr_SetString(PyExc_TypeError, "Key should be a str"); + return -1; + } + PyObject *bytes_str = PyUnicode_AsASCIIString(_key); + if(PyErr_Occurred()) + { + return -1; + } + const char *key = PyBytes_AS_STRING(bytes_str); + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + rpn_if_default_data_t *rif_data = (rpn_if_default_data_t*)expr_self->rif->params->data; + + Py_ssize_t idx = -1; + Py_ssize_t res_idx = -1; + + switch(rif_data->pos_flag) + { + case RPN_IF_POSITION_XY: + res_idx = 2; + if(key[1] != '\0') { break; } + switch(key[0]) + { + case 'X': + idx=0; + break; + case 'Y': + idx=1; + break; + } + break; + case RPN_IF_POSITION_LINEAR: + res_idx = 1; + if(!strcmp("X", key)) + { + idx = 0; + } + break; + case RPN_IF_POSITION_XDIM: + size_t ndim = rif_data->size_lim[0]; + res_idx = ndim; + char possible_key[64]; + for(size_t i=0; ires_flag) + { + case RPN_IF_RES_BOOL: + case RPN_IF_RES_XFUN: + if(!strcmp("R", key)) + { + idx = res_idx; + } + break; + case RPN_IF_RES_RGBA: + if(!strcmp("A", key)) + { + idx = res_idx + 3; + } + case RPN_IF_RES_RGB: + if(key[1] != '\0') + { + break; + } + switch(key[0]) + { + case 'R': + idx=res_idx; + break; + case 'G': + idx=res_idx+1; + break; + case 'B': + idx=res_idx+2; + break; + } + break; + default: + // not implemented, not unknown.... + PyErr_SetString(PyExc_RuntimeError, + "UNKOWN RES_FLAG"); + return -1; + } + } + return idx; +} + +PyObject* rpnif_subscript(PyObject *self, PyObject *key) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + + Py_ssize_t idx = _rpnif_subscript_idx(self, key); + if(idx < 0) + { + PyErr_Format(PyExc_IndexError, + "No expression '%R' with given parameters", + key); + return NULL; + } + PyObject *expr = PyTuple_GET_ITEM(expr_self->expr, idx); + Py_INCREF(expr); + return expr; +} + +int rpnif_ass_subscript(PyObject *self, PyObject *key, PyObject *elt) +{ + Py_ssize_t idx = _rpnif_subscript_idx(self, key); + if(idx < 0) + { + PyErr_Format(PyExc_IndexError, + "Cannot set expression '%R' that do not exists with this parameters", + key); + return -1; + } + + return rpnif_expr_ass_item(self, idx, elt); +} + +int rpnif_getbuffer(PyObject *self, Py_buffer *view, int flags) +{ + PyRPNIterExpr_t *expr_self; + expr_self = (PyRPNIterExpr_t*)self; + + if(expr_self->mmap) + { + return PyObject_GetBuffer(expr_self->mmap, view, flags); + } + return _rpnif_getbuffer_nopymap(self, expr_self->rif->params, + expr_self->_mmap, view, flags); +} + +void rpnif_releasebuffer(PyObject *self, Py_buffer *view) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + if(expr_self->mmap) + { + return PyBuffer_Release(view); + } + return _rpnif_releasebuffer_nopymap(self, view); +} + +PyObject* rpnif_str(PyObject *self) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + rpn_if_default_data_t *rif_data = \ + (rpn_if_default_data_t*)expr_self->rif->params->data; + const size_t rpn_sz = expr_self->rif->params->rpn_sz; + const char pyfmt[] = "%VA%ld=%U=\"%S\";"; + + size_t i; + PyObject *buf, *tmp, *items; + + buf = NULL; + items = rpnif_items(self); + if(PyErr_Occurred()) { return NULL; } + + + for(i=0; ires_flag) + { + case RPN_IF_RES_CONST: + tmp = PyUnicode_FromFormat("%U A%ld=C(*)=%lld", + buf, i+1, rif_data->const_val[0]); + Py_DECREF(buf); + buf=tmp; + break; + case RPN_IF_RES_CONST_RGBA: + tmp = PyUnicode_FromFormat("%U A%ld=R(*)=%lld A%ld=G(*)=%lld A%ld=B(*)=%lld A%ld=A(*)=%lld", + buf, + i+1, rif_data->const_val[0], + i+2, rif_data->const_val[1], + i+3, rif_data->const_val[2], + i+4, rif_data->const_val[3]); + Py_DECREF(buf); + buf=tmp; + break; + } + + Py_INCREF(buf); + return buf; +} + +/** Return a string representation of expressions dict + * @param self @ref PyRPNIterExpr_t instance + * @return a str instance */ +static PyObject* _rpnif_expr_repr(PyObject *self) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + rpn_if_default_data_t *rif_data = \ + (rpn_if_default_data_t*)expr_self->rif->params->data; + + PyObject *buf, *tmp, *items, *const_res; + const char pyfmt[] = "%V\"%U\":\"%S\"%V"; + + buf = const_res = NULL; + + items = rpnif_items(self); + if(PyErr_Occurred()) { return NULL; } + +#define _const_res_key ",\"const_res\":" + switch(rif_data->res_flag) + { + case RPN_IF_RES_CONST: + const_res = PyUnicode_FromFormat( + _const_res_key "{\"r\":%llu}}", + rif_data->const_val[0]); + break; + /* + case RPN_IF_RES_CONST_RGB: + const_res = PyUnicode_FromFormat( + "\"const_res\": {\"r\":%llu, \"g\":%llu, \"b\":%llu}}", + rif_data->const_val[0], + rif_data->const_val[1], + rif_data->const_val[2]) + break; + */ + case RPN_IF_RES_CONST_RGBA: + const_res = PyUnicode_FromFormat( + _const_res_key \ + "{\"r\":%llu,\"g\":%llu,\"b\":%llu,\"a\":%llu}}", + rif_data->const_val[0], + rif_data->const_val[1], + rif_data->const_val[2], + rif_data->const_val[3]); + break; + default: + const_res = PyUnicode_FromFormat("}"); + break; + } +#undef _const_res_key + + const size_t rpn_sz = expr_self->rif->params->rpn_sz; + + for(size_t i=0; irif->params->data; + + char *str_pos, *str_res; + char tmp[64]; + + PyObject *expr_repr = _rpnif_expr_repr(self); + if(PyErr_Occurred()) { return NULL; } + + switch(rif_data->pos_flag) + { + case RPN_IF_POSITION_XY: + str_pos = "XY"; + break; + case RPN_IF_POSITION_LINEAR: + str_pos = "LINEAR"; + break; + case RPN_IF_POSITION_XDIM: + snprintf(tmp, 64, "XDIM%ld", rif_data->size_lim[0]); + str_pos = tmp; + break; + default: + PyErr_SetString(PyExc_RuntimeError, + "UNKOWN POS_FLAG2"); + return NULL; + } + + PyObject *const_res = NULL; + + switch(rif_data->res_flag) + { + case RPN_IF_RES_BOOL: + str_res = "BOOL"; + break; + case RPN_IF_RES_CONST: + str_res = "CONST"; + const_res = PyTuple_New(1); + break; + case RPN_IF_RES_CONST_RGBA: + str_res = "CONST_RGBA"; + break; + case RPN_IF_RES_COUNT: + str_res = "COUNT"; + break; + case RPN_IF_RES_XFUN: + str_res = "XFUN"; + break; + case RPN_IF_RES_RGB: + str_res = "RGB"; + break; + case RPN_IF_RES_RGBA: + str_res = "RGBA"; + break; + default: + PyErr_SetString(PyExc_RuntimeError, + "UNKOWN RES_FLAG2"); + return NULL; + } + + PyObject *res; + + const_res = const_res?const_res:Py_None; + + res = PyUnicode_FromFormat( + "", + str_pos, str_res, expr_repr, const_res); + if(PyErr_Occurred()) { return NULL; } + return res; +} + +PyObject* rpnif_getstate(PyObject *self, PyObject *noargs) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + const rpn_if_param_t *params = expr_self->rif->params; + rpn_if_default_data_t *data = (rpn_if_default_data_t*)(params->data); + + const size_t const_val_sz = data->res_flag == RPN_IF_RES_CONST ? 1 : \ + (data->res_flag == RPN_IF_RES_CONST_RGBA ? 4 : 0); + + const size_t nszlim = data->ndim + + (data->pos_flag == RPN_IF_POSITION_XDIM ?1:0); + + + size_t buf_sz = sizeof(void*) + \ + sizeof(rpn_if_param_t) + \ + sizeof(rpn_if_default_data_t) + \ + sizeof(rpn_value_t) * ( \ + params->rpn_sz + params->rpn_argc) + \ + sizeof(size_t) * nszlim + \ + sizeof(rpn_value_t) * const_val_sz; + + // all stack allocation grouped here + void *new_rpn[params->rpn_sz]; + + size_t rpn_sz[params->rpn_sz]; + + size_t sz_max = 0; + for(size_t i=0; irpn_sz; i++) + { + rpn_sz[i] = rpn_expr_serialize(&expr_self->rif->rpn[i], + NULL, 0); + // each expression stores a size and a picled repr + buf_sz += rpn_sz[i] + sizeof(size_t); + if(rpn_sz[i] > sz_max) { sz_max = rpn_sz[i]; } + } + + void *buf = malloc(buf_sz); + if(!buf) + { + PyErr_Format(PyExc_MemoryError, + "Unable to allocate pickled representation : ", + strerror(errno)); + return NULL; + } + + bzero(buf, buf_sz); + bzero(new_rpn, sizeof(*new_rpn)); + + void **ptr = buf; // mmap ptr + rpn_if_param_t *new_params = (void*)(ptr+1); + rpn_if_default_data_t *new_data = (void*)(new_params + 1); + size_t *new_size_lim = (void*)(new_data+1); + rpn_value_t *new_const_val = (void*)(new_size_lim + nszlim); + size_t *new_rpn_sz = (void*)(new_const_val + const_val_sz); + size_t cur_offset = sizeof(size_t)*params->rpn_sz; + + *ptr = expr_self->_mmap; + + new_params->mem_sz = params->mem_sz; + new_params->value_sz = params->value_sz; + new_params->rpn_sz = params->rpn_sz; + new_params->rpn_argc = params->rpn_argc; + new_params->rpn_stack_sz = params->rpn_stack_sz; + // The following should be more efficient, but result + // in some badly initialized bytes in new_params struct + // padding... + //*new_params = *params; + + new_params->getarg_f = NULL; + new_params->setres_f = NULL; + new_params->data = NULL; + + new_data->pos_flag = data->pos_flag; + new_data->res_flag = data->res_flag; + new_data->ndim = data->ndim; + // Same as above + //*new_data = *data; + + new_data->size_lim = NULL; + new_data->const_val = NULL; + + memcpy(new_size_lim, data->size_lim, sizeof(size_t)*nszlim); + if(const_val_sz) + { + memcpy(new_const_val, data->const_val, sizeof(rpn_value_t)*const_val_sz); + } + + // set sizes & rpn expressions + for(size_t i=0; irpn_sz; i++) + { + new_rpn[i] = ((void*)new_rpn_sz)+cur_offset; + new_rpn_sz[i] = rpn_sz[i]; + bzero(new_rpn[i], rpn_sz[i]); + cur_offset += rpn_sz[i]; + if(!rpn_expr_serialize(&expr_self->rif->rpn[i], + new_rpn[i], rpn_sz[i])) + { + goto err_rpn; + } + } + + PyObject *res = PyBytes_FromStringAndSize(buf, buf_sz); + return res; + +err_rpn: + /**@todo handle error */ + free(buf); + return NULL; +} + +PyObject* rpnif_setstate(PyObject *self, PyObject *state_bytes) +{ + PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self; + + if(!PyBytes_Check(state_bytes)) /* Arg check */ + { + PyErr_SetString(PyExc_TypeError, "Expected bytes"); + return NULL; + } + + size_t bsize = PyBytes_GET_SIZE(state_bytes); + const void *data = (void*)PyBytes_AS_STRING(state_bytes); + + if(bsize < sizeof(void*)) + { + PyErr_SetString(PyExc_ValueError, "Smaller than expected given"); + return NULL; + } + + rpn_if_param_t *params, *ser_params; + rpn_if_default_data_t *ser_data; + size_t *ser_size_lim; + rpn_value_t *ser_const_val; + + expr_self->mmap = NULL; + expr_self->_mmap = *(void**)data; + + ser_params = (void*)((void**)data+1); + ser_data = (void*)(ser_params+1); + ser_size_lim = (void*)(ser_data+1); + + const size_t nszlim = ser_data->ndim + \ + (ser_data->pos_flag == RPN_IF_POSITION_XDIM ?1:0); + + ser_const_val = (void*)(ser_size_lim + nszlim); + + params = rpn_if_default_params(ser_data->pos_flag, ser_data->res_flag, + ser_size_lim, ser_const_val, + ser_params->rpn_stack_sz); + + const size_t const_val_sz = ser_data->res_flag == RPN_IF_RES_CONST?1:\ + (ser_data->res_flag == RPN_IF_RES_CONST_RGBA?4:0); + + rpn_expr_t *ser_rpn = malloc(sizeof(*ser_rpn) * ser_params->rpn_sz); + if(!ser_rpn) + { + PyErr_Format(PyExc_MemoryError, + "Unable to alloc rpn expressions : %s", + strerror(errno)); + return NULL; + } + size_t *ser_rpn_sz = (void*)(ser_const_val + const_val_sz); + void *ser_rpn_buf = (void*)(ser_rpn_sz + ser_params->rpn_sz); + + + for(size_t i=0; irpn_sz; i++) + { + if(rpn_expr_deserialize(&ser_rpn[i], ser_rpn_buf, ser_rpn_sz[i]) < 0) + { + PyErr_Format(PyExc_ValueError, + "Unable to deserialize expr#%ld : %s", + i, ser_rpn[i].err_reason); + return NULL; + } + ser_rpn_buf = (void*)ser_rpn_buf + ser_rpn_sz[i]; + } + + expr_self->rif = rpn_if_new(params, expr_self->_mmap, ser_rpn); + if(!expr_self->rif) + { + PyErr_SetString(PyExc_ValueError, + "Unable to initialize rif expression"); + return NULL; + } + expr_self->rif_params = params; + expr_self->ndim = ser_data->ndim; + if(_rpnif_init_expr_tuple(expr_self) < 0) + { + return NULL; + } + expr_self->borrowed_if = 0; // explicitly not borrowed + + Py_RETURN_NONE; +} + +rpn_if_param_t *_rpnif_get_params(unsigned short pos_flag, unsigned short res_flag, + PyObject *lim_obj, PyObject *const_values_obj, + unsigned short stack_size) +{ + char err_str[256]; + int ndim; + + // Args checking + if(stack_size < 4 || stack_size > 255) + { + snprintf(err_str, 128, + "Stack size should be in [0..255] but %u given", + stack_size); + PyErr_SetString(PyExc_ValueError, err_str); + return NULL; + } + + // 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 NULL; + } + + //Check & convert lim + PyObject *tmp; + tmp = PySequence_Fast(lim_obj, "Sequence expected for size_lim argument"); + if(PyErr_Occurred()) + { + return NULL; + } + Py_ssize_t lim_obj_sz = PySequence_Fast_GET_SIZE(tmp); + ndim = lim_obj_sz; + if(PyErr_Occurred()) + { + Py_DECREF(tmp); + return NULL; + } + if(lim_obj_sz < 1) + { + Py_DECREF(tmp); + PyErr_SetString(PyExc_ValueError, + "Size limits cannot be empty"); + return NULL; + } + + if(pos_flag == RPN_IF_POSITION_XDIM) + { + PyObject *item = PySequence_Fast_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"); + Py_DECREF(tmp); + return NULL; + } + 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); + Py_DECREF(tmp); + return NULL; + } + 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); + Py_DECREF(tmp); + return NULL; + } + } + + size_t sz_limits[lim_obj_sz]; + for(Py_ssize_t i = 0; i 0) + { + tmp = const_values_obj; + if(!PyTuple_Check(tmp)) + { + PyErr_SetString(PyExc_ValueError, + "Invalid type for const_values argument"); + return NULL; + } + 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 NULL; + } + } + + rpn_value_t const_values[values_obj_sz]; + for(Py_ssize_t i = 0; idata); + + 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->rpn_argc); + PyStructSequence_SET_ITEM(res, 0, val); + + val = PyLong_FromLong(params->pos_flag); + PyStructSequence_SET_ITEM(res, 1, val); + + val = PyLong_FromLong(params->res_flag); + PyStructSequence_SET_ITEM(res, 2, 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, 3, lim); + + for(Py_ssize_t i=0; isize_lim[i]); + PyTuple_SET_ITEM(lim, i, val); + } + + if(!params->const_val) + { + Py_INCREF(Py_None); + PyTuple_SET_ITEM(res, 4, Py_None); + } + else + { + PyObject *values = PyTuple_New(expt_sizes[1]); + if(!values) + { + Py_DECREF(res); + return NULL; + } + PyStructSequence_SET_ITEM(res, 4, values); + for(Py_ssize_t i=0; iconst_val[i]); + PyTuple_SET_ITEM(values, i, val); + } + } + val = PyLong_FromUnsignedLong(_params->mem_sz * _params->value_sz); + PyStructSequence_SET_ITEM(res, 5, val); + return res; +} + + +PyObject *_rpnif_to_pos(rpn_if_default_data_t *rif_data, PyObject** argv, Py_ssize_t argc) +{ + const short idx_off = rif_data->pos_flag == RPN_IF_POSITION_XDIM?1:0; + long long value; + + if((size_t)argc != rif_data->ndim) + { + PyErr_Format(PyExc_IndexError, + "Expression expect %lu dimentions coordinates," + " but %ld arguments given.", + rif_data->ndim, argc); + return NULL; + } + + rpn_value_t result=0; + size_t cur_dim_sz = 1; + + for(size_t i=0; indim; i++) + { + if(!PyLong_Check(argv[i])) + { + PyErr_SetString(PyExc_TypeError, + "coordinates must be integers"); + return NULL; + } + value = PyLong_AsLongLong(argv[i]); + if(value < 0) + { + value = (-(rpn_value_t)-value)%rif_data->size_lim[i+idx_off]; + } + if((size_t)value >= rif_data->size_lim[i+idx_off]) + { + PyErr_Format(PyExc_IndexError, + "Coordinate %ld overflows : %R/%lu", + i, argv[i], + rif_data->size_lim[i+idx_off]); + return NULL; + } + result += value * cur_dim_sz; + cur_dim_sz *= rif_data->size_lim[i+idx_off]; + } + + return PyLong_FromUnsignedLongLong(result); +} + + +PyObject *_rpnif_from_pos(rpn_if_default_data_t *rif_data, PyObject* _pos) +{ + const short idx_off = rif_data->pos_flag == RPN_IF_POSITION_XDIM?1:0; + + if(!PyLong_Check(_pos)) + { + PyErr_SetString(PyExc_TypeError, + "Expected position to be an integer"); + return NULL; + } + + size_t pos = PyLong_AsUnsignedLong(_pos); + if(PyErr_Occurred()) + { + return NULL; + } + + PyObject *res = PyTuple_New(rif_data->ndim); + if(PyErr_Occurred()) + { + return NULL; + } + + for(size_t i=0; indim; i++) + { + size_t val; + size_t lim = rif_data->size_lim[i+idx_off]; + + val = pos % lim; + pos /= lim; + + PyObject *elt = PyLong_FromUnsignedLong(val); + if(PyErr_Occurred()) + { + goto err; + } + PyTuple_SET_ITEM(res, i, elt); + } + + return res; + +err: + Py_DECREF(res); + return NULL; +} + + +int _rpnif_getbuffer_nopymap(PyObject *self, const rpn_if_param_t *params, + void *buf, Py_buffer *view, int flags) +{ + rpn_if_default_data_t *data = (rpn_if_default_data_t*)params->data; + + view->buf = buf; + view->obj = self; + view->len = params->mem_sz * params->value_sz; + view->readonly = 0; + view->itemsize = sizeof(rpn_value_t); + view->format = (flags & PyBUF_FORMAT)?"L":NULL; + view->ndim = 1; + view->shape = NULL; + if(flags & PyBUF_ND) + { + short nval; + switch(data->res_flag) + { + case RPN_IF_RES_BOOL: + case RPN_IF_RES_CONST: + case RPN_IF_RES_COUNT: + nval = 1; + break; + case RPN_IF_RES_RGBA: + case RPN_IF_RES_CONST_RGBA: + nval = 4; + break; + case RPN_IF_RES_RGB: + nval = 3; + break; + default: + PyErr_SetString(PyExc_BufferError, + "Unsupported result flag"); + return -1; + } + view->ndim += (nval>1)?1:0; + if(!(view->shape = malloc(sizeof(Py_ssize_t) * view->ndim))) + { + PyErr_Format(PyExc_BufferError, + "Buffer's shape allocation error : %s", + strerror(errno)); + return -1; + } + for(size_t i=0; indim; i++) + { + const int idx = i + \ + (data->pos_flag == RPN_IF_POSITION_XDIM)?1:0; + view->shape[i] = data->size_lim[idx]; + } + if(nval>1) + { + view->shape[data->ndim] = nval; + } + + } + view->strides = NULL; + view->suboffsets = NULL; + Py_INCREF(self); + return 0; +} + + +void _rpnif_releasebuffer_nopymap(PyObject *self, Py_buffer *view) +{ + if(view->shape) { free(view->shape); } + view->buf = NULL; + Py_DECREF(self); +} + + +int _rpnif_init_expr_tuple(PyRPNIterExpr_t *expr_self) +{ + expr_self->expr = PyTuple_New(expr_self->rif->params->rpn_sz); + for(size_t i=0; irif->params->rpn_sz;i++) + { + rpn_expr_t *expr = &expr_self->rif->rpn[i]; + PyObject *instance = rpnexpr_init_borrowing(expr); + if(!instance) + { + //! @todo set exception + return -1; + } + PyTuple_SET_ITEM(expr_self->expr, i, instance); + } + return 0; +} + + +/**@def _ret_append(key) + * @hiderefs + * local macro */ +/**@def _const_res_key() + * @hiderefs + * local macro*/ + diff --git a/python_if.h b/python_if.h index ba6c721..149251b 100644 --- a/python_if.h +++ b/python_if.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Weber Yann + * Copyright (C) 2020,2023 Weber Yann * * This file is part of pyrpn. * @@ -28,10 +28,22 @@ #include "structmember.h" #include "rpn_if.h" -#incdlue "pyton_rpnexpr.h" +#include "rpn_if_default.h" +#include "rpn_if_mutate.h" +#include "python_mutation.h" +#include "python_rpnexpr.h" +#include "python_const.h" -/**@defgroup python_if RPN Iterated Function Python class - * @ingroup python_module +/**@file python_if.h + * @brief Python RPNIterExpr type headers + * @ingroup python_ext + * @ingroup pymod_pyrpn_RPNExprIter + * + * This file is the header of the RPNIterExpr Python class + */ + +/**@defgroup pymod_pyrpn_RPNExprIter class pyrpn.RPNExprIter + * @ingroup pymod_pyrpn * @brief Exposed Python class : RPNIterExpr * * Iterated expression are composed of RPN expressions, they are able to @@ -41,41 +53,316 @@ * @see ifs_if */ -/**@file python_if.h - * @brief Python RPNIterExpr type headers - * @ingroup python_if - * - * This file is the header of the RPNIterExpr Python class - */ - -/**@brief RPNIterExpr Python class methods list - * @ingroup python_if */ -extern PyMethodDef RPNIterExpr_methods[]; -/**@brief RPNIterExpr Python class members list - * @ingroup python_if */ -extern PyMemberDef RPNIterExpr_members[]; /**@brief RPNIterExpr Python class type definition - * @ingroup python_if */ + * @ingroup pymod_pyrpn_RPNExprIter */ extern PyTypeObject RPNIterExprType; +/**@brief Points on Python's std mmap module */ +extern PyObject *mmap_module; +/**@brief Python's mmap.mmap class */ +extern PyObject *mmap_cls; + /**@brief Structure holding RPNIterExpr objects - * @ingroup python_if */ + * + * @warning Pickling methods are implemented for a limited usage. + * The mmap.mmap instance olded by the structure (sadly) cannot be + * pickled... In order to allow using RPNExprIter instances in + * multiprocessing stuffs, the piclking method will "export" only + * a pointer on the shared mmap. This implies that if the GC pops out + * on the mmap, the unpickled instance will segfault... + * This implies that unpickled instances has limited capabilities (no + * way to get back the mmap.mmap instance). + * There is a way (using another shared mmap at module level ?) to + * implement a custom reference count on the mmap pointer in order + * to determine when to DECREF the mmap.mmap instance. + * + * @todo think about implementing a safe pickle/unpickle method + * @ingroup pymod_pyrpn_RPNExprIter */ typedef struct { + /** Python's type definition */ PyObject_VAR_HEAD; + /** @brief Number of dimention in map */ + size_t ndim; + /**@brief Pointer on @ref rpn_if_s */ rpn_if_t *rif; + + /**@brief Python tuple with instances of RPNExpr */ + PyObject *expr; + + /**@brief Python mmap.mmap instance representing rif memory map + * @note NULL if unpickled instance */ + PyObject *mmap; + + /**@brief Memory map buffer allowing acces to underlying pointer + * @note unrelevant if unpickled instance */ + Py_buffer mm_buff; + + /**@brief Memory map buffer pointer */ + void *_mmap; + + /**@brief Pointer on params to free at del */ + rpn_if_param_t *rif_params; + + /**@brief If true, someone else (an ifs ?) will take care of freeing + * the if at deletion */ + short borrowed_if; + } PyRPNIterExpr_t; +/**@brief RPNIterExpr.params static method + * @param args Position arguments + * @param kwds Keyword arguments + * @return A new rpnif_params_SeqDesc instance + */ +PyObject* rpnif_params(PyObject *cls, PyObject *args, PyObject *kwds); + +/**@brief RpnIterExpr __new__ method + * @param subtype Type of object being created (pyrpn.RPNIterExpr) + * @param args positional arguments for subtype + * @param kwds keyword argumenrs for subtype + * @return The new Python RPNIterExpr object + * @ingroup pymod_pyrpn_RPNExprIter + */ +PyObject* rpnif_new(PyTypeObject *subtype, PyObject* args, PyObject* kwds); + /**@brief RpnIterExpr constructor - * @param self New RPNExpr instance + * @param self New RPNIterExpr instance * @param args Positional arguments list * @param kwds Keywords arguments dict * @return 0 if no error else -1 - * @ingroup python_if + * @ingroup pymod_pyrpn_RPNExprIter */ -int rpnexpr_init(PyObject *self, PyObject *args, PyObject *kwds); +int rpnif_init(PyObject *self, PyObject *args, PyObject *kwds); + +/**@brief RPNIterExpr constructor with a borrowed expression + * @param borrowed_if Pointer on borrowed instance + * @param mmap Pointer on borrowed python mmap object (NULL if deserialized instance) + * @return 0 if no error else -1 + */ +PyObject* rpnif_init_borrowed(rpn_if_t *borrowed_if, PyObject *mmap); + +/**@brief RPNIterExpr __del__ method + * @param self RPNExpr instance + * @ingroup pymod_pyrpn_RPNExprIter + */ +void rpnif_del(PyObject *self); + +/**@brief Buffer protocol request handler method + * @param self RPNIterExpr instance + * @param view A memoryview to fill depending on flags + * @param flags Indicates the request type + * @return -1 on error + * @note Used by RPNIterExpr and RPNIFS (proper way to implement it is base class) + * See python documentation at + * https://docs.python.org/3/c-api/buffer.html#buffer-request-types + * @ingroup pymod_pyrpn_RPNExprIter + */ +int rpnif_getbuffer(PyObject *self, Py_buffer *view, int flags); + +/**@brief Buffer protocol request release method + * @param self RPNIterExppr instance + * @param view The memoryview to release (free what we filled) + * See python documentation at + * https://docs.python.org/3/c-api/buffer.html#buffer-request-types + * @ingroup pymod_pyrpn_RPNExprIter + */ +void rpnif_releasebuffer(PyObject *self, Py_buffer *view); + +/**@brief Return a named tuple of custom rif data + * @param self RPNIterExpr instance + * @return A RPNIterParams named tuple + * @ingroup pymod_pyrpn_RPNExprIter + */ +PyObject *rpnif_get_params(PyObject *self); + +/**@brief Set the mmap object used for data storage + * @param self RPNIterExpr instance + * @return None + * @ingroup pymod_pyrpn_RPNExprIter + */ +PyObject *rpnif_set_mmap(PyObject *self, PyObject *mm_obj); + +/**@brief Return a tuple with data buffer's shape + * @param self RPNIterExpr instance + * @return A new ref on a tuple + * @ingroup pymod_pyrpn_RPNExprIter + */ +PyObject *rpnif_shape(PyObject *self); + +/**@brief Runs an IF on given position + * @param self RPNInterExpr instance + * @param pos The start position + * @return A new position + * @ingroup pymod_pyrpn_RPNExprIter + */ +PyObject *rpnif_step(PyObject *self, PyObject* pos); + +/**@brief Mutate the expressions in the IF + * @note For X mutations, an expression will be choosen randomly and mutate between + * 1 and X times, if mutations left to do we loop on expression choice. + * @param self + * @param args + * @param kwargs + * @return None + */ +PyObject* rpnif_mutate(PyObject *self, PyObject *args, PyObject *kwargs); + +/**@brief Convert given arguments coordinates to position + * @param self RPNIterExpr instance + * @param argv Pointer on the array of arguments + * @param argc Argument count + * @return An integer + */ +PyObject *rpnif_to_pos(PyObject *self, PyObject** argv, Py_ssize_t argc); + +/**@brief Convert given position to coordinates tuple + * @param self RPNIterExpr instance + * @param pos Position, as integer, to convert + * @return A tuple + */ +PyObject *rpnif_from_pos(PyObject *self, PyObject* pos); + +/**@brief RPNIterExpr __getstate__ method for pickling + * @param cls RPNIterExpr type object + * @param noargs Not an argument... + * @return A bytes Python instance suitable as argument for + * @ref rpnexpr_setstate + * @ingroup pymod_pyrpn_RPNExprIter + */ +PyObject* rpnif_getstate(PyObject *cls, PyObject *noargs); + +/**@brief RPNIterExpr __setstate__ method for pickling + * @param cls RPNIterExpr type object + * @param state Should by a bytes Python instance returned by @ref + * rpnexpr_getstate + * @return A bytes Python instance suitable as argument for + * @ref rpnexpr_setstate + * @ingroup pymod_pyrpn_RPNExprIter + */ +PyObject* rpnif_setstate(PyObject *cls, PyObject *state); + +/**@brief RPNIterExpr.__repr__() + * @param self RPNIterExpr instance + * @return A string representation of the instance + * @ingroup pymod_pyrpn_RPNExprIter + */ +PyObject* rpnif_repr(PyObject *self); + +/**@brief RPNIterExpr.__str__() + * @param self RPNIterExpr instance + * @return A string representation of the instance + * @ingroup pymod_pyrpn_RPNExprIter + */ +PyObject* rpnif_str(PyObject *self); + +/**@brief RPNExpr.__len__() method + * @param self RPNIterExpr instance + * @return A integer with the number of tokens in expression + * @ingroup pymod_pyrpn_RPNExprIter + */ +Py_ssize_t rpnif_len(PyObject *self); + +/**@brief Sequence method for __getitem__ method + * + * Allow expressions access with integer indexes + * @param self RPNIterExpr instance + * @param idx Item index + * @return Corresponding RPNExpr or raise IndexError + * @ingroup pymod_pyrpn_RPNExprIter + */ +PyObject* rpnif_expr_item(PyObject *self, Py_ssize_t idx); + +/**@brief Sequence method for __setitem__ method + * + * Allow to recompile an expressions using a str with integer indexes + * @param self RPNIterExpr instance + * @param idx Item index + * @param elt The code (str) to compile in indicated expression + * @return 0 if no error else -1 + * @ingroup pymod_pyrpn_RPNExprIter + */ +int rpnif_expr_ass_item(PyObject *self, Py_ssize_t idx, PyObject* elt); + +/**@brief Mapping method to access expressions by name + * @param self RPNIterExpr instance + * @param key An expression name (depending on parameters) + * @return The corresponding RPNExpr of raise KeyError + * @ingroup pymod_pyrpn_RPNExprIter + */ +PyObject* rpnif_subscript(PyObject *self, PyObject *key); + +/**@brief Mapping method to recompile an expression by name + * @param self RPNIterExpr instance + * @param key An expression name (depending on parameters) + * @param elt The code (str) to compile in indicated expression + * @return 0 if no error else -1 + * @ingroup pymod_pyrpn_RPNExprIter + */ +int rpnif_ass_subscript(PyObject *self, PyObject *key, PyObject *elt); + +/**@brief Mimics dict.keys() method + * @param self RPNIterExpr instance + * @return A tuple with expressions name + * @ingroup pymod_pyrpn_RPNExprIter */ +PyObject *rpnif_keys(PyObject *self); + +/**@brief Mimics dict.values() method + * @param self RPNIterExpr instance + * @return A tuple with RPNExpr instances + * @ingroup pymod_pyrpn_RPNExprIter */ +PyObject *rpnif_values(PyObject *self); + +/**@brief Mimics dict.items() method + * @param self RPNIterExpr instance + * @return A tuple with tuples(key, value) + * @ingroup pymod_pyrpn_RPNExprIter */ +PyObject *rpnif_items(PyObject *self); + +/**@brief Returns rpn_if_params given arguments + * @param pos_flag a position flag + * @param res_flag a result flag + * @param size_lim a tuple with size limits + * @param const_values a tuple wiith constant values + * @param stack_size a stack size + */ +rpn_if_param_t *_rpnif_get_params(unsigned short pos_flag, unsigned short res_flag, + PyObject *size_lim, PyObject *const_values, + unsigned short stack_size); + +/**@brief Converts rpn_if_params to named tuple + * @param params The rpnif params + * @return A named tuple + */ +PyObject *_rpnif_params_to_tuple(const rpn_if_param_t *params); + + +/**@brief Implementation of both RPNIterExpr.to_pos and RPNIFS.to_pos */ +PyObject *_rpnif_to_pos(rpn_if_default_data_t *rif_data, PyObject** argv, Py_ssize_t argc); + +/**@brief Implementation of bot RPNIterExpr.to_pos and RPNIFS.from_pos */ +PyObject *_rpnif_from_pos(rpn_if_default_data_t *rif_data, PyObject* _pos); + + +/**@brief Buffer protocol request handler method when no python mmap exist + * (deserialized instance) + * @param self RPNIterExpr instance + * @param view A memoryview to fill depending on flags + * @param flags Indicates the request type + * @return -1 on error + * @note Used by RPNIterExpr and RPNIFS (proper way to implement it is base class) + * See python documentation at + * https://docs.python.org/3/c-api/buffer.html#buffer-request-types + * @ingroup pymod_pyrpn_RPNExprIter + */ +int _rpnif_getbuffer_nopymap(PyObject *self, const rpn_if_param_t *params, + void *buf, Py_buffer *view, int flags); +void _rpnif_releasebuffer_nopymap(PyObject *self, Py_buffer *view); + +/**@todo doc */ +int _rpnif_init_expr_tuple(PyRPNIterExpr_t *expr_self); #endif diff --git a/python_ifs.c b/python_ifs.c new file mode 100644 index 0000000..d6bf8bd --- /dev/null +++ b/python_ifs.c @@ -0,0 +1,684 @@ +/* + * Copyright (C) 2020,2023 Weber Yann + * + * This file is part of pyrpn. + * + * pyrpn is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * pyrpn is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with pyrpn. If not, see . + */ +#include "python_ifs.h" + +/**@file python_ifs.c */ + +static PyMethodDef RPNIFS_methods[] = { + PYRPN_method("weights", rpnifs_weights, + METH_FASTCALL, + "self, weights=None", + "Get or set the weights list.\n\n\ +When setting the weight list, the weight list len sets the number of functions \ +in the system."), + PYRPN_method("position", rpnifs_position, + METH_FASTCALL, + "self, position=None", + "Get or set the current position"), + PYRPN_method("run", rpnifs_run, + METH_FASTCALL, + "self, steps, rand_bytes=None, /", + "Run ifs steps times"), + PYRPN_method("to_pos", rpnifs_to_pos, + METH_FASTCALL, + "self, *args, /", + "Return a position (int) from a coordinates given as" + "argument."), + PYRPN_method("from_pos", rpnifs_from_pos, + METH_O, + "self, position, /", + "Return a coordinates tuple from given position."), + PYRPN_method("set_mutation_params", rpnifs_set_mutation_params, + METH_VARARGS | METH_KEYWORDS, + "self, params=None, /, weight_type=None, \ +mutation_weight_range=None, if_weights=None, if_component_weights=None, \ +if_mut_params=None", + "Set mutation parameters. \n\ +The first argument 'params' is for giving an IFSMutationParams \ +(self.ifs_mutation_weights) instance.\n\ +Keywords arguments corresponds to IFSMutationParams writable fields \ +(see help(pyrpn.IFSMutationParams)).\n\n\ +Note : if params and keywords arguments are given, params is used and \ +then overwrited by given keywords arguments."), + {NULL} // Sentinel +}; + +static PyMemberDef RPNIFS_members[] = { + {"mutation_params", T_OBJECT, + offsetof(PyRPNIFS_t, py_ifs_mut_weights), READONLY, + "The mutation weights & parameters for mutating the IFS"}, + {"expressions", T_OBJECT, offsetof(PyRPNIFS_t, rpn_if), READONLY, + "The tuple with RPNIterExpr instances"}, + {"mmap", T_OBJECT, offsetof(PyRPNIFS_t, mmap), READONLY, + "The mmap storing data"}, + {NULL} // sentinel +}; + +static PySequenceMethods RPNIFS_seq_methods = { + .sq_length = rpnifs_len, + .sq_item = rpnifs_expr_item, + .sq_ass_item = rpnifs_expr_ass_item, +}; + +static PyBufferProcs RPNIFS_as_buffer = { + (getbufferproc)rpnifs_getbuffer, + (releasebufferproc)rpnifs_releasebuffer, +}; + + +PyTypeObject RPNIFSType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "pyrpn.RPNIFS", + .tp_basicsize = sizeof(PyRPNIFS_t), + .tp_itemsize = 0, + .tp_del = rpnifs_del, + .tp_repr = rpnifs_repr, + .tp_as_sequence = &RPNIFS_seq_methods, + .tp_str = rpnifs_str, + .tp_as_buffer = &RPNIFS_as_buffer, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = "RPN IFS", + .tp_methods = RPNIFS_methods, + .tp_members = RPNIFS_members, + .tp_init = rpnifs_init, + .tp_new = rpnifs_new, +}; + +PyObject *rpnifs_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds) +{ + PyObject *ret, *err; + PyRPNIFS_t *ifs_self; + ret = PyType_GenericNew(subtype, args, kwds); + if((err = PyErr_Occurred())) + { + Py_DECREF(err); + return ret; + } + ifs_self = (PyRPNIFS_t*)ret; + ifs_self->rpn_if = NULL; + ifs_self->rpn_expr = NULL; + ifs_self->rifs = NULL; + ifs_self->mmap = NULL; + + return ret; +} + +/**@brief Initialize internal fields */ +static int _rpnifs_init_weights(PyObject *self) +{ + PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self; + ifs_mutation_weights_t *imw = &ifs_self->ifs_mut_weights; + + if(ifs_mutation_weights_alloc(imw, ifs_self->ifs, 0, 0) < 0) + { + PyErr_Format(PyExc_RuntimeError, + "Error allocating ifs mutation weights : %s", + strerror(errno)); + return -1; + } + + imw->w_mut_type[0] = imw->w_mut_type[1] = 0.5; + imw->w_weight_range[0] = 0.01; + imw->w_weight_range[1] = 0.1; + + for(size_t i=0; i < ifs_self->ifs->if_sz; i++) + { + imw->w_mut_if[i] = 1.0/ifs_self->ifs->if_sz; + } + for(size_t i=0; i < imw->w_comp_if_sz; i++) + { + imw->w_comp_if[i] = 1.0/imw->w_comp_if_sz; + } + + memcpy(imw->if_mut_params, &rpn_mutation_params_default, + sizeof(rpn_mutation_params_default)); + + if(ifs_mutation_weights_update(imw) < 0) + { + PyErr_Format(PyExc_RuntimeError, + "Error while updating mutation weights : '%s'\n", + strerror(errno)); + return -1; + } + + if(pyrpn_ifs_mut_weight_to_pyobj(imw, + &ifs_self->py_ifs_mut_weights) < 0) + { + return -1; + } + + return 0; +} + +int rpnifs_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + PyRPNIFS_t *ifs_self; + char *names[] = {"pos_flag", "res_flag", "size_lim", + "const_values", "stack_size", "mmap", NULL}; + unsigned short pos_flag, res_flag, stack_size; + PyObject *lim_obj, *const_values_obj, *mmap_obj; + + ifs_self = (PyRPNIFS_t*)self; + + // default values + stack_size = 16; + const_values_obj = lim_obj = mmap_obj = NULL; + ifs_self->rifs = NULL; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, "HHO|OHO:RPNIFS.__init__", + names, + &pos_flag, &res_flag, &lim_obj, &const_values_obj, + &stack_size, &mmap_obj)) + { + return -1; + } + + rpn_if_param_t *rif_params = _rpnif_get_params(pos_flag, res_flag, + lim_obj, const_values_obj, stack_size); + if(!rif_params) + { + return -1; + } + + const Py_ssize_t expt_sz = rif_params->mem_sz * rif_params->value_sz; + if(!mmap_obj) + { + PyObject *fileno = PyLong_FromLong(-1); + PyObject *length = PyLong_FromSize_t(expt_sz); + mmap_obj = PyObject_CallFunctionObjArgs(mmap_cls, fileno, length, + NULL); + Py_DECREF(fileno); + Py_DECREF(length); + if(PyErr_Occurred()) + { + return -1; + } + } + else + { + if(!PyObject_TypeCheck(mmap_obj, (PyTypeObject*)mmap_cls)) + { + PyErr_SetString(PyExc_TypeError, + "The mmap argument MUST be an instance \ +of mmap.mmap"); + return -1; + } + /**@todo check if mmap is shared & writable ? */ + if(PyObject_Length(mmap_obj) != (Py_ssize_t)expt_sz) + { + PyErr_Format(PyExc_ValueError, + "Expected mmap lenght is %ld but mmap with \ +length %ld provided", + rif_params->mem_sz, PyObject_Length(mmap_obj)); + return -1; + } + } + + if(PyObject_GetBuffer(mmap_obj, &ifs_self->mm_buff, + PyBUF_CONTIG) == -1) + { + return -1; + } + + ifs_self->mmap = mmap_obj; + ifs_self->_mmap = ifs_self->mm_buff.buf; + + ifs_self->ifs = rpn_ifs_new(rif_params, ifs_self->mm_buff.buf); + if(!ifs_self->ifs) + { + PyErr_Format(PyExc_RuntimeError, + "Error initializing ifs: %s", strerror(errno)); + return -1; + } + + ifs_self->rpn_if = NULL; + if(_rpnifs_update_if_tuple(ifs_self) < 0) + { + return -1; + } + + if(_rpnifs_init_weights(self) < 0) + { + return -1; + } + return 0; +} + + +void rpnifs_del(PyObject *self) +{ + PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self; + + rpn_ifs_free(ifs_self->ifs); + free(ifs_self->ifs); + if(ifs_self->mmap) + { + if(ifs_self->mm_buff.buf) + { + PyBuffer_Release(&ifs_self->mm_buff); + ifs_self->mm_buff.buf = NULL; + } + Py_DECREF(ifs_self->mmap); + ifs_self->mmap = NULL; + } +} + + +PyObject *rpnifs_to_pos(PyObject *self, PyObject** argv, Py_ssize_t argc) +{ + PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self; + rpn_if_default_data_t *rif_data = (rpn_if_default_data_t*)ifs_self->ifs->params.data; + return _rpnif_to_pos(rif_data, argv, argc); +} + + +PyObject *rpnifs_from_pos(PyObject *self, PyObject* _pos) +{ + PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self; + rpn_if_default_data_t *rif_data = (rpn_if_default_data_t*)ifs_self->ifs->params.data; + return _rpnif_from_pos(rif_data, _pos); +} + + +PyObject *rpnifs_weights(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self; + PyObject *iter; + unsigned int *weights; + if(nargs == 1 && args[0] != Py_None) + { + // Try to update weights + const Py_ssize_t len = PyObject_Length(args[0]); + if(PyErr_Occurred()) + { + return NULL; + } + /* + if((size_t)len != ifs_self->ifs->if_sz) + { + PyErr_Format(PyExc_ValueError, + "Expected %ld weights but argument length is %ld", + ifs_self->ifs->if_sz, len); + return NULL; + } + */ + weights = malloc(sizeof(unsigned int)*len); + if(len && !weights) + { + PyErr_Format(PyExc_MemoryError, + "Unable to allocate weights : %s", + strerror(errno)); + return NULL; + } + iter = PyObject_GetIter(args[0]); + if(PyErr_Occurred()) + { + goto err_weights; + } + for(size_t i=0; i<(unsigned int)len; i++) + { + PyObject *item = PyIter_Next(iter); + if(PyErr_Occurred()) + { + goto err_weights; + } + const unsigned long w = PyLong_AsUnsignedLong(item); + Py_DECREF(item); + if(PyErr_Occurred()) + { + pyrpn_PyErr_ReraiseFormat(PyExc_ValueError, + "\ +Weights sequence element #%ld invalid", i); + goto err_weights; + } + weights[i] = w; + } + Py_DECREF(iter); + rpn_ifs_set_if_count(ifs_self->ifs, len, weights); + free(weights); + } + + // construct returned weights in a new tuple + PyObject *ret = PyTuple_New(ifs_self->ifs->if_sz); + if(PyErr_Occurred()) + { + return NULL; + } + for(size_t i=0; iifs->if_sz; i++) + { + const unsigned int weight = ifs_self->ifs->weight[i]; + PyObject *item = PyLong_FromUnsignedLong(weight); + if(PyErr_Occurred()) { return NULL; } + PyTuple_SET_ITEM(ret, i, item); + } + + if(_rpnifs_update_if_tuple(ifs_self) < 0) + { + return NULL; + } + if(ifs_mutation_weights_if_count_update(&ifs_self->ifs_mut_weights, + ifs_self->ifs->if_sz) < 0) + { + PyErr_Format(PyExc_RuntimeError, + "Error reallocating mutation weights : %s", + strerror(errno)); + return NULL; + } + Py_DECREF(ifs_self->py_ifs_mut_weights); + if(pyrpn_ifs_mut_weight_to_pyobj(&ifs_self->ifs_mut_weights, + &ifs_self->py_ifs_mut_weights) < 0) + { + return NULL; + } + return ret; + +err_weights: + free(weights); + Py_DECREF(iter); + return NULL; +} + + +PyObject *rpnifs_position(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self; + + if(nargs == 1 && args[0] != Py_None) + { + size_t pos = PyLong_AsSize_t(args[0]); + if(PyErr_Occurred()) { return NULL; } + if(pos > ifs_self->ifs->params.mem_sz) + { + pos %= ifs_self->ifs->params.mem_sz; +#ifndef NDEBUG + PyErr_WarnFormat(PyExc_RuntimeWarning, 1, + "Given position %R overflows and stored as %lu", + args[0], pos); +#endif + } + ifs_self->ifs->pos = pos; + } + + return PyLong_FromSize_t(ifs_self->ifs->pos); +} + +PyObject *rpnifs_run(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self; + if(nargs == 0 || nargs > 2) + { + PyErr_Format(PyExc_ValueError, + "Expected 1 or 2 arguments but %ld given", + nargs); + return NULL; + } + + size_t steps = PyLong_AsSize_t(args[0]); + if(PyErr_Occurred()) + { + return NULL; + } + else if(steps == 0) + { + PyErr_Format(PyExc_ValueError, + "Steps arguments must be > 0"); + return NULL; + } + + unsigned char *rnd_buf = NULL; + if(nargs == 2) + { + // Python bytes given as random source + const Py_ssize_t sz = PyBytes_Size(args[1]); + if(PyErr_Occurred()) { goto err; } + if((size_t)sz != sizeof(char)*steps) + { + PyErr_Format(PyExc_ValueError, + "Given random bytes are too small." + "Expected %ld bytes but got %ld", + sizeof(char)*steps, + sz); + goto err; + } + rnd_buf = (unsigned char*)PyBytes_AsString(args[1]); + } + + const int ret = rpn_ifs_run(ifs_self->ifs, steps, rnd_buf); + if(ret < 0) + { + PyErr_Format(PyExc_RuntimeError, + "Unable to run IFS : %s", + strerror(errno)); + return NULL; + } + + Py_RETURN_NONE; + +err: + return NULL; +} + + +PyObject *rpnifs_set_mutation_params(PyObject *self, PyObject *args, + PyObject *kwds) +{ + PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self; + + char *names[] = {"params", "weight_type", "mutation_weight_range", + "if_weights", "if_component_weights", "if_mut_params", NULL}; + + PyObject *params = NULL; + PyObject *mut_type_weight = NULL, \ + *weight_range = NULL, \ + *mut_if_weight = NULL, \ + *if_comp_weight = NULL, \ + *mutation_params = NULL; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, + "|OOOOOO:RPNIFS.set_mutation_weight", + names, + ¶ms, &mut_type_weight, + &weight_range, &mut_if_weight, &if_comp_weight, + &mutation_params)) + { + return NULL; + } + + if(params && params != Py_None) + { + ifs_mutation_weights_t tmp; + if(pyrpn_pyobj_to_ifs_mutation_weights(params, &tmp) < 0) + { + pyrpn_PyErr_ReraiseFormat(PyExc_ValueError, + "params argument invalid"); + return NULL; + } + if(tmp.if_count != ifs_self->ifs->if_sz) + { + PyErr_Format(PyExc_ValueError, + "Expected params for %ld IF but given \ +params is for %ld IF.", ifs_self->ifs->if_sz, tmp.if_count); + + } + ifs_mutation_weights_dealloc(&ifs_self->ifs_mut_weights); + memcpy(&ifs_self->ifs_mut_weights, &tmp, sizeof(tmp)); + } + + // handling kwds, overwriting optional params argument value + if(mut_type_weight && mut_type_weight != Py_None) + { + if(pyrpn_ifs_mut_params_mut_type_weight( + &ifs_self->ifs_mut_weights, + mut_type_weight) < 0) + { + pyrpn_PyErr_ReraiseFormat(PyExc_ValueError, + "Error with weight_type argument"); + return NULL; // TODO check if nothing to clean + } + } + if(weight_range && weight_range != Py_None) + { + if(pyrpn_ifs_mut_params_weight_range( + &ifs_self->ifs_mut_weights, + weight_range) < 0) + { + pyrpn_PyErr_ReraiseFormat(PyExc_ValueError, + "Error with mutation_weight_range argument"); + return NULL; // TODO check if nothing to clean + } + } + + if(mut_if_weight && mut_if_weight != Py_None) + { + if(pyrpn_ifs_mut_params_weight_mut_if(&ifs_self->ifs_mut_weights, + mut_if_weight, 0) < 0) + { + pyrpn_PyErr_ReraiseFormat(PyExc_ValueError, + "Error with if_weights parameters"); + return NULL; // TODO howto clean ? + } + } + + if(if_comp_weight && if_comp_weight != Py_None) + { + if(pyrpn_ifs_mut_params_if_comp_weight(&ifs_self->ifs_mut_weights, + if_comp_weight) < 0) + { + pyrpn_PyErr_ReraiseFormat(PyExc_ValueError, + "Error with if_comp_weight parameter"); + return NULL; // TODO hwoto clean ? + } + } + + if(mutation_params && mutation_params != Py_None) + { + if(pyrpn_ifs_mut_params_mutation_params(&ifs_self->ifs_mut_weights, + mutation_params) < 0) + { + pyrpn_PyErr_ReraiseFormat(PyExc_ValueError, + "Error with mutation_params argument"); + return NULL; // TODO howto clean ? + } + } + + // Construct the python repr of the params + Py_DECREF(ifs_self->py_ifs_mut_weights); + ifs_self->py_ifs_mut_weights = NULL; + + if(pyrpn_ifs_mut_weight_to_pyobj(&ifs_self->ifs_mut_weights, + &ifs_self->py_ifs_mut_weights) < 0) + { + return NULL; + } + + Py_RETURN_NONE; +} + +PyObject *rpnifs_str(PyObject *self) +{ + PyErr_SetString(PyExc_NotImplementedError, "TODO"); + return NULL; +} + + +PyObject *rpnifs_repr(PyObject *self) +{ + PyErr_SetString(PyExc_NotImplementedError, "TODO"); + return NULL; +} + + +Py_ssize_t rpnifs_len(PyObject *self) +{ + PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self; + return (Py_ssize_t)ifs_self->ifs->if_sz; +} + + +PyObject *rpnifs_expr_item(PyObject *self, Py_ssize_t idx) +{ + PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self; + Py_ssize_t _idx = idx; + + if(idx < 0) + { + _idx = ifs_self->ifs->if_sz - 1 + idx; + } + if(_idx < 0 || (size_t) _idx >= ifs_self->ifs->params.rpn_sz) + { + PyErr_Format(PyExc_IndexError, + "There is %ld expressions in the system but index %ld asked", + ifs_self->ifs->params.rpn_sz, idx); + return NULL; + } + PyObject *ret = PyTuple_GET_ITEM(ifs_self->rpn_if, _idx); + Py_INCREF(ret); + return ret; +} + + +int rpnifs_expr_ass_item(PyObject *self, Py_ssize_t idx, PyObject *value) +{ + PyErr_SetString(PyExc_NotImplementedError, "TODO"); + return -1; +} + + +int rpnifs_getbuffer(PyObject *self, Py_buffer *view, int flags) +{ + PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self; + + if(ifs_self->mmap) + { + return PyObject_GetBuffer(ifs_self->mmap, view, flags); + } + return _rpnif_getbuffer_nopymap(self, &ifs_self->ifs->params, + ifs_self->_mmap, view, flags); +} + +void rpnifs_releasebuffer(PyObject *self, Py_buffer *view) +{ + PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self; + if(ifs_self->mmap) + { + return PyBuffer_Release(view); + } + return _rpnif_releasebuffer_nopymap(self, view); +} + +int _rpnifs_update_if_tuple(PyRPNIFS_t *ifs_self) +{ + if(ifs_self->rpn_if) + { + Py_DECREF(ifs_self->rpn_if); + } + ifs_self->rpn_if = PyTuple_New(ifs_self->ifs->if_sz); + for(size_t i=0; iifs->if_sz; i++) + { + PyObject *py_if = rpnif_init_borrowed(ifs_self->ifs->rpn_if[i], + ifs_self->mmap); + if((!py_if) || PyErr_Occurred()) + { + return -1; + } + PyTuple_SET_ITEM(ifs_self->rpn_if, i, py_if); + } + return 0; +} + diff --git a/python_ifs.h b/python_ifs.h new file mode 100644 index 0000000..9c879ed --- /dev/null +++ b/python_ifs.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2020,2023 Weber Yann + * + * This file is part of pyrpn. + * + * pyrpn is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * pyrpn is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with pyrpn. If not, see . + */ +#ifndef _PYTHON_IFS_H__ +#define _PYTHON_IFS_H__ + +#include "config.h" + +#include + +#define PY_SSIZE_T_CLEAN +#include +#include "structmember.h" + +#include "rpn_if.h" +#include "rpn_if_default.h" +#include "rpn_ifs.h" +#include "python_rpnexpr.h" +#include "python_if.h" +#include "python_const.h" + + +/**@file python_ifs.h */ + +/** @brief RPNIFS Python class type definition */ +extern PyTypeObject RPNIFSType; + +/**@brief Points on Python's std mmap module */ +extern PyObject *mmap_module; +/**@brief Python's mmap.mmap class */ +extern PyObject *mmap_cls; + +/** @brief Structure holding RPNIFS attributes */ +typedef struct +{ + PyObject_VAR_HEAD; + + /** @brief Tuple with RPNIterExpr instances */ + PyObject *rpn_if; + + /** @brief Tuple with RPNExpr instances */ + PyObject *rpn_expr; + + /** @brief IFS pointer */ + rpn_ifs_t *ifs; + + /** @brief Pointer on IF expressions in the system */ + rpn_if_t **rifs; + + + PyObject *py_ifs_mut_weights; + + ifs_mutation_weights_t ifs_mut_weights; + + + /**@brief Python mmap.mmap instance representing rif memory map + * @note NULL if unpickled instance */ + PyObject *mmap; + + /**@brief Memory map buffer allowing acces to underlying pointer + * @note unrelevant if unpickled instance */ + Py_buffer mm_buff; + + /**@brief Memory map buffer pointer */ + void *_mmap; + +} PyRPNIFS_t; + + +PyObject *rpnifs_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds); +int rpnifs_init(PyObject *self, PyObject *args, PyObject *kwds); +void rpnifs_del(PyObject *self); + + + +PyObject *rpnifs_weights(PyObject *self, PyObject *const *args, Py_ssize_t nargs); +PyObject *rpnifs_position(PyObject *self, PyObject *const *args, Py_ssize_t nargs); +PyObject *rpnifs_run(PyObject *self, PyObject *const *args, Py_ssize_t nargs); + +PyObject *rpnifs_set_mutation_params(PyObject *self, PyObject *args, + PyObject *kwargs); + +PyObject *rpnifs_to_pos(PyObject *self, PyObject** argv, Py_ssize_t argc); +PyObject *rpnifs_from_pos(PyObject *self, PyObject* _pos); + +PyObject *rpnifs_str(PyObject *self); +PyObject *rpnifs_repr(PyObject *self); + +Py_ssize_t rpnifs_len(PyObject *self); +PyObject *rpnifs_expr_item(PyObject *self, Py_ssize_t idx); +int rpnifs_expr_ass_item(PyObject *self, Py_ssize_t idx, PyObject *value); + +int rpnifs_getbuffer(PyObject *self, Py_buffer *view, int flags); +void rpnifs_releasebuffer(PyObject *self, Py_buffer *view); + +int _rpnifs_update_if_tuple(PyRPNIFS_t *ifs_self); + + +#endif diff --git a/python_mutation.c b/python_mutation.c new file mode 100644 index 0000000..2680bca --- /dev/null +++ b/python_mutation.c @@ -0,0 +1,702 @@ +#include "python_mutation.h" + + +/**@brief @ref pymod_pyrpn_RPNMutationParams field definition + * @ingroup pymod_pyrpn_RPNMutationParams */ +static PyStructSequence_Field mutation_params_fields[] = { + { .name = "minimum_length", + .doc = "Expression minimum length", + }, + { .name = "weight_add", + .doc = "The weight for 'add token' mutation", + }, + { .name = "weight_del", + .doc = "The weight for 'delete token' mutation", + }, + { .name = "weight_mut", + .doc = "The weight for 'mutate token' mutation", + }, + { .name = "weight_mut_soft", + .doc = "The weight for 'mutate soft (value not type) token' mutation", + }, + { + .name = "weight_add_elt", + .doc = "The weights for each token types when adding a token", + }, + { + .name = "weight_del_elt", + .doc= "The weights for each token types when deleting a token", + }, + { NULL, NULL}, +}; +PyStructSequence_Desc rpn_mutation_params_desc = { + .name = "RPNMutationParams", + .doc = "Named tuple for mutation parameters", + .n_in_sequence = (sizeof(mutation_params_fields) / \ + sizeof(*mutation_params_fields)) - 1, + .fields = mutation_params_fields, +}; +PyTypeObject rpn_mutation_params_SeqDesc; + + +static PyStructSequence_Field ifs_mutation_weights_fields[] = { + { .name = "weight_type", + .doc = "Two element defining weight for random choice of \ +mutation type : (Weight_Mutation_weight, IF_Mutation_weight).", + }, + { .name = "mutation_weight_range", + .doc = "Min & max values for weight modifications. A random \ +float will be choosen in [min..max] and will be randomly \ +added/substracted to a randomly choosen weight", + }, + { .name = "if_count", + .doc = "Stores the number of IF in the system to mutate", + }, + { .name = "if_weights", + .doc = "Indicate weights (chance) for each IF to be mutated", + }, + { .name = "if_component_weights", + .doc = "Can be an array of weight, indicating the chance \ +for an IF component to be mutated. Else can be an array of array of weight, \ +allowing custom component weights for each expression.", + }, + { .name = "if_mut_params", + .doc = "Stores 1 global mutation params or a list of mutation \ +params named tuple (one per IF)", + }, + { NULL, NULL}, +}; + +PyStructSequence_Desc ifs_mutation_weights_desc = { + .name = "IFSMutationParams", + .doc = "Named tuple for mutation parameters", + .n_in_sequence = (sizeof(ifs_mutation_weights_fields) / \ + sizeof(*ifs_mutation_weights_fields)) - 1, + .fields = ifs_mutation_weights_fields, +}; +PyTypeObject ifs_mutation_weights_SeqDesc; + + +int pyrpn_python_mutation_init(PyObject *pyrpn_mod) +{ + rpn_mutation_init_params(&rpn_mutation_params_default); + + // Named tuple for mutation parameters + PyStructSequence_InitType(&rpn_mutation_params_SeqDesc, + &rpn_mutation_params_desc); + if(PyErr_Occurred()) { return -1; } + Py_INCREF(&rpn_mutation_params_SeqDesc); + if(PyModule_AddObject(pyrpn_mod, "RPNMutationParamsTuple", + (PyObject*)&rpn_mutation_params_SeqDesc) < 0) + { + goto err; + } + + // Named tuple for ifs mutation parameters + PyStructSequence_InitType(&ifs_mutation_weights_SeqDesc, + &ifs_mutation_weights_desc); + if(PyErr_Occurred()) { + dprintf(2, "FAILED SEQUENCE INITTYPE %d '%s'\n", + ifs_mutation_weights_desc.n_in_sequence, + ifs_mutation_weights_desc.fields[5].doc); + goto err; + } + Py_INCREF(&ifs_mutation_weights_SeqDesc); + if(PyModule_AddObject(pyrpn_mod, "IFSMutationParams", + (PyObject*)&ifs_mutation_weights_SeqDesc) < 0) + { + goto err2; + } + return 0; + +err2: + Py_DECREF(&ifs_mutation_weights_SeqDesc); +err: + Py_DECREF(&rpn_mutation_params_SeqDesc); + return -1; +} + +/**@brief Parse given object to float + * @param obj The python object to parse + * @param res Points on result + * @note Raise python exception on error + */ +static inline void _parse_float(PyObject *obj, float *res) +{ + double tmp = PyFloat_AsDouble(obj); + if(tmp > FLT_MAX) + { + PyErr_SetString(PyExc_OverflowError, "Float overflows"); + return; + } + *res=tmp; + return; +} + +/** Parse weights python parameter + * @param obj The python object + * @param w Points on the 3 weights to parse + * @note Raise python exception on error + */ +static inline void _parse_type_weights(PyObject *obj, float w[3]) +{ + PyObject *fast = PySequence_Fast(obj, "Not a RPNTokenTypeTuple nor with a length of 3"); + if(!fast) { return; } + if(PySequence_Length(obj) != 3) + { + PyErr_Format(PyExc_ValueError, "Excpected RPNTokenTypeTuple or 3 elements but got %d elements", PySequence_Length(obj)); + return; + } + + PyObject **elts = PySequence_Fast_ITEMS(fast); + + const char* names[3] = {"op", "const", "var"}; + for(size_t i=0; i<3; i++) + { + _parse_float(elts[i], &w[i]); + if(PyErr_Occurred()) + { + PyErr_Format(PyExc_ValueError, + "Bad value for .%s field", + names[i]); + break; + } + } + return; +} + +static inline void _parse_floats(PyObject *obj, float *w, size_t len) +{ + PyObject *fast; + fast = PySequence_Fast(obj, "A sequence is expected"); + if(!fast || PyErr_Occurred()) { return; } + + Py_INCREF(fast); + if((size_t)PySequence_Length(obj) != len) + { + PyErr_Format(PyExc_ValueError, + "Expected %ld element but %ld given", + len, PySequence_Length(fast)); + goto ret; + } + PyObject **elt = PySequence_Fast_ITEMS(fast); + + for(size_t i=0; imin_len = PyLong_AsSize_t(elts[0]); + if(PyErr_Occurred()) + { + PyErr_SetString(PyExc_ValueError, "Bad type for .minimum_length field"); + return -1; + } + + _parse_float(elts[1], ¶ms->w_add); + if(PyErr_Occurred()) + { + PyErr_SetString(PyExc_ValueError, "Bad value for .weight_add field"); + return -1; + } + _parse_float(elts[2], ¶ms->w_del); + if(PyErr_Occurred()) + { + PyErr_SetString(PyExc_ValueError, "Bad value for .weight_del field"); + return -1; + } + _parse_float(elts[3], ¶ms->w_mut); + if(PyErr_Occurred()) + { + PyErr_SetString(PyExc_ValueError, "Bad value for .weight_mut field"); + return -1; + } + _parse_float(elts[4], ¶ms->w_mut_soft); + if(PyErr_Occurred()) + { + PyErr_SetString(PyExc_ValueError, "Bad value for .weight_mut_soft field"); + return -1; + } + + _parse_type_weights(elts[5], params->w_add_elt); + if(PyErr_Occurred()) + { + PyErr_SetString(PyExc_ValueError, "Bad value for .weight_add_elt field"); + return -1; + } + + _parse_type_weights(elts[6], params->w_mut_elt); + if(PyErr_Occurred()) + { + PyErr_SetString(PyExc_ValueError, "Bad value for .weight_add_elt field"); + return -1; + } + rpn_mutation_init_params(params); + return 0; +} + + +int pyrpn_pyobj_to_ifs_mutation_weights(PyObject *py_params, + ifs_mutation_weights_t *w) +{ + bzero(w, sizeof(*w)); + if(!PySequence_Check(py_params)) + { + PyErr_SetString(PyExc_TypeError, + "The given object is not a sequence"); + return -1; + } + if(PySequence_Size(py_params) != 6) + { + PyErr_Format(PyExc_ValueError, + "Expect a sequence of 5 elements but given sequence \ +contains %ld elements", PySequence_Size(py_params)); + return -1; + } + PyObject *fast = PySequence_Fast(py_params, + "Unable to use as IFSMutationWeightsTuple"); + if(PyErr_Occurred()) { return -1; } + Py_INCREF(fast); + + PyObject **elts = PySequence_Fast_ITEMS(fast); + + if(pyrpn_ifs_mut_params_mut_type_weight(w, elts[0]) < 0) + { + pyrpn_PyErr_ReraiseFormat(PyExc_ValueError, + "Error with weight_type field #0"); + return -1; + } + if(pyrpn_ifs_mut_params_weight_range(w, elts[1]) < 0) + { + pyrpn_PyErr_ReraiseFormat(PyExc_ValueError, + "Error with weight_type field #1"); + return -1; + } + w->if_count = PyLong_AsSize_t(elts[2]); + if(PyErr_Occurred()) + { + pyrpn_PyErr_ReraiseFormat(PyExc_ValueError, + "Error with IF count field #2"); + return -1; + } + + if(pyrpn_ifs_mut_params_weight_mut_if(w, elts[3], 1) < 0) + { + pyrpn_PyErr_ReraiseFormat(PyExc_ValueError, + "Error with IFS IF mutation weights field #3"); + return -1; + } + + if(pyrpn_ifs_mut_params_if_comp_weight(w, elts[4]) < 0) + { + pyrpn_PyErr_ReraiseFormat(PyExc_ValueError, + "Error with IFS IF component mutation \ +weight field #4"); + goto err_free_w_mut_if; + } + + if(pyrpn_ifs_mut_params_mutation_params(w, elts[5]) < 0) + { + pyrpn_PyErr_ReraiseFormat(PyExc_ValueError, + "Error with IFS RPN mutation params field #5"); + goto err_free_comp_weight; + } + + + return 0; + +err_free_comp_weight: + free(w->w_comp_if); + w->w_comp_if = NULL; + w->w_comp_if_sz = 0; +err_free_w_mut_if: + free(w->w_mut_if); + w->w_mut_if = NULL; + w->if_count = 0; + return -1; + +} + + +int pyrpn_mutation_params_to_pyobj(const rpn_mutation_params_t *params, + PyObject **res) +{ + PyObject *val; + Py_ssize_t pos; + + *res = PyStructSequence_New(&rpn_mutation_params_SeqDesc); + if(PyErr_Occurred()) + { + goto err; + } + Py_INCREF(*res); + + pos = 0; + val = PyLong_FromSize_t(params->min_len); + if(PyErr_Occurred()) { goto err_ref; } + Py_INCREF(val); + PyStructSequence_SetItem(*res, pos, val); + + pos++; + val = PyFloat_FromDouble(params->w_add); + if(PyErr_Occurred()) { goto err_ref; } + Py_INCREF(val); + PyStructSequence_SetItem(*res, pos, val); + + pos++; + val = PyFloat_FromDouble(params->w_del); + if(PyErr_Occurred()) { goto err_ref; } + Py_INCREF(val); + PyStructSequence_SetItem(*res, pos, val); + + pos++; + val = PyFloat_FromDouble(params->w_mut); + if(PyErr_Occurred()) { goto err_ref; } + Py_INCREF(val); + PyStructSequence_SetItem(*res, pos, val); + + pos++; + val = PyFloat_FromDouble(params->w_mut_soft); + if(PyErr_Occurred()) { goto err_ref; } + Py_INCREF(val); + PyStructSequence_SetItem(*res, pos, val); + + pos++; + val = Py_BuildValue("(f,f,f)", + params->w_add_elt[0], + params->w_add_elt[1], + params->w_add_elt[2]); + if(PyErr_Occurred()) { goto err_ref; } + Py_INCREF(val); + PyStructSequence_SetItem(*res, pos, val); + + pos++; + val = Py_BuildValue("(f,f,f)", + params->w_mut_elt[0], + params->w_mut_elt[1], + params->w_mut_elt[2]); + if(PyErr_Occurred()) { goto err_ref; } + Py_INCREF(val); + PyStructSequence_SetItem(*res, pos, val); + // add elt & del elt + + return 0; +err_ref: + Py_DECREF(*res); +err: + *res = NULL; + return -1; +} + + +int pyrpn_ifs_mut_weight_to_pyobj(const ifs_mutation_weights_t *w, + PyObject **res) +{ + PyObject *val; + Py_ssize_t pos; + + *res = PyStructSequence_New(&ifs_mutation_weights_SeqDesc); + if(PyErr_Occurred()) + { + goto err; + } + Py_INCREF(*res); + + pos = 0; + val = Py_BuildValue("(f, f)", w->w_mut_type[0], w->w_mut_type[1]); + if(PyErr_Occurred()) { goto ref_err; } + Py_INCREF(val); + PyStructSequence_SetItem(*res, pos, val); + + pos++; + val = Py_BuildValue("(f, f)", w->w_weight_range[0], + w->w_weight_range[1]); + if(PyErr_Occurred()) { goto ref_err; } + Py_INCREF(val); + PyStructSequence_SetItem(*res, pos, val); + + pos++; + val = PyLong_FromSize_t(w->if_count); + if(PyErr_Occurred()) { goto ref_err; } + Py_INCREF(val); + PyStructSequence_SetItem(*res, pos, val); + + pos++; + val = PyTuple_New(w->if_count); + if(PyErr_Occurred()) { goto ref_err; } + Py_INCREF(val); + for(size_t i=0; iif_count; i++) + { + PyObject *elt = PyFloat_FromDouble(w->w_mut_if[i]); + if(PyErr_Occurred()) { goto err_val; } + Py_INCREF(elt); + PyTuple_SET_ITEM(val, i, elt); + } + PyStructSequence_SetItem(*res, pos, val); + + pos++; + val = PyTuple_New(w->w_comp_if_sz); + if(PyErr_Occurred()) { goto ref_err; } + Py_INCREF(val); + for(size_t i=0; i < w->w_comp_if_sz; i++) + { + PyObject *elt = PyFloat_FromDouble(w->w_comp_if[i]); + if(PyErr_Occurred()) { goto err_val; } + Py_INCREF(elt); + PyTuple_SET_ITEM(val, i, elt); + } + PyStructSequence_SetItem(*res, pos, val); + + pos++; + if(!w->custom_params) + { + if(pyrpn_mutation_params_to_pyobj(w->if_mut_params, &val) < 0) + { + goto ref_err; + } + } + else + { + val = PyTuple_New(w->if_count); + for(size_t i=0; iif_count; i++) + { + PyObject *elt; + if(pyrpn_mutation_params_to_pyobj(&(w->if_mut_params[i]), + &elt) < 0) + { + goto err_val; + } + PyTuple_SET_ITEM(val, i, elt); + } + } + PyStructSequence_SetItem(*res, pos, val); + + return 0; + +err_val: + Py_DECREF(val); +ref_err: + Py_DECREF(*res); +err: + *res = NULL; + return -1; +} + + +int pyrpn_ifs_mut_params_mut_type_weight(ifs_mutation_weights_t *w, + PyObject *mut_type_weight) +{ + _parse_floats(mut_type_weight, w->w_mut_type, 2); + return PyErr_Occurred() ? -1 : 0; +} + + +int pyrpn_ifs_mut_params_weight_range(ifs_mutation_weights_t *w, + PyObject *weight_range) +{ + _parse_floats(weight_range, w->w_weight_range, 2); + return PyErr_Occurred() ? -1 : 0; +} + +int pyrpn_ifs_mut_params_weight_mut_if(ifs_mutation_weights_t *w, + PyObject *mut_if_weight, short do_realloc) +{ + const size_t len = PySequence_Length(mut_if_weight); + short alloc = 0; + if(PyErr_Occurred()) + { + return -1; + } + if(len != w->if_count) + { + PyErr_Format(PyExc_ValueError, + "Trying to set IF mutation weight in a system \ +with %ld expression with a sequence of %ld weights", w->if_count, len); + return -1; + } + if(!w->w_mut_if) + { + alloc = 1; + if(!(w->w_mut_if = malloc(sizeof(*w->w_mut_if) * len))) + { + PyErr_Format(PyExc_MemoryError, + "Unable to allocate IFS IF mutation \ +weights for %ld expressions", len); + return -1; + } + } + else if(do_realloc) + { + alloc = 1; + void *tmp = realloc(w->w_mut_if, sizeof(*w->w_mut_if) * len); + if(!tmp) + { + PyErr_Format(PyExc_MemoryError, + "Unable to reallocate IFS IF mutation \ +weights for %ld expressiosn", len); + return -1; + } + w->w_mut_if = tmp; + } + _parse_floats(mut_if_weight, w->w_mut_if, len); + if(PyErr_Occurred()) + { + if(alloc) + { + // TODO not good when realloc + free(w->w_mut_if); + w->if_count = 0; + } + return -1; + } + return 0; +} + + +int pyrpn_ifs_mut_params_if_comp_weight(ifs_mutation_weights_t *w, + PyObject *if_comp_weight) +{ + const size_t len = PySequence_Length(if_comp_weight); + if(PyErr_Occurred()) + { + return -1; + } + if(!len) + { + PyErr_SetString(PyExc_ValueError, + "Invalid IFS IF component mutation weights :\ +empty sequence"); + return -1; + } + if_mutation_weight_t *old = NULL; + size_t old_len = w->w_comp_if_sz; + if(len != w->w_comp_if_sz) + { + old = w->w_comp_if; + if(!(w->w_comp_if = malloc(sizeof(*w->w_comp_if) * len))) + { + PyErr_Format(PyExc_MemoryError, + "Unable to allocate IFS IF component \ +mutation weights : %s", strerror(errno)); + return -1; + } + w->w_comp_if_sz = len; + } + _parse_floats(if_comp_weight, w->w_comp_if, w->w_comp_if_sz); + if(PyErr_Occurred()) + { + if(old) + { + free(w->w_comp_if); + w->w_comp_if = old; + w->w_comp_if_sz = old_len; + } + return -1; + } + return 0; +} + +int pyrpn_ifs_mut_params_mutation_params(ifs_mutation_weights_t *w, + PyObject *mutation_params) +{ + rpn_mutation_params_t tmp_params; + // Try + if(pyrpn_pyobj_to_mutation_params(mutation_params, &tmp_params) == 0) + { + // single RPNMutationParams converted + void *tmp = realloc(w->if_mut_params, + sizeof(*w->if_mut_params)); + if(!tmp) + { + PyErr_Format(PyExc_MemoryError, + "Enable to reallocate IFS RPN \ +mutation params for a single parameter"); + return -1; + } + w->if_mut_params = tmp; + + w->custom_params = 0; + memcpy(w->if_mut_params, &tmp_params, sizeof(tmp_params)); + return 0; + } + // Except + // A sequence of RPNMutationParams is given + if(PyErr_Occurred()) { PyErr_Clear(); } + + const size_t len = (size_t)PySequence_Length(mutation_params); + if(PyErr_Occurred()) { return -1; } + + if(len != w->if_count && len != 1) + { + PyErr_Format(PyExc_ValueError, + "Invalid lenght for IFS RPN mutation \ +params sequence lenght : should be 1 or %ld or a valid RPNMutationParams, \ +but a sequence of length %ld given.", w->if_count, len); + return -1; + } + + void *tmp = realloc(w->if_mut_params, + sizeof(*w->if_mut_params) * len); + if(!tmp) + { + PyErr_Format(PyExc_MemoryError, + "Enable to reallocate IFS RPN \ +mutation params array : %s", strerror(errno)); + return -1; + } + w->if_mut_params = tmp; + + PyObject *fast_params = PySequence_Fast(mutation_params, + "IFS RPN Mutation parameters \ +should be a sequence"); + if(!fast_params) { return -1; } + Py_INCREF(fast_params); + + PyObject **mut_params = PySequence_Fast_ITEMS(fast_params); + + for(size_t i=0; i < len; i++) + { + if(pyrpn_pyobj_to_mutation_params(mut_params[i], + &w->if_mut_params[i]) < 0) + { + pyrpn_PyErr_ReraiseFormat( + PyExc_ValueError, + "\ +Error with IFS RPN mutation params #%ld that is not a valid RPNMutationParams"); + return -1; + } + } + w->custom_params = len == 1 ? 0 : 1; + return 0; +} + diff --git a/python_mutation.h b/python_mutation.h new file mode 100644 index 0000000..2fd2303 --- /dev/null +++ b/python_mutation.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2020 Weber Yann + * + * This file is part of pyrpn. + * + * pyrpn is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * pyrpn is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with pyrpn. If not, see . + */ +#ifndef _PYTHON_MUTATION_H__ +#define _PYTHON_MUTATION_H__ + +#include "config.h" + +#include +#include + +#define PY_SSIZE_T_CLEAN +#include +#include "structmember.h" + +#include "python_const.h" +#include "rpn_mutate.h" +#include "rpn_if_mutate.h" +#include "rpn_ifs_mutate.h" + +/**@file python_mutation.h + * @brief Python types (named tuple) & utility to manipulate mutation + * parameters + * @ingroup python_ext + */ + + +/**@defgroup pymod_pyrpn_RPNMutationParams pyrpn.RPNMutationParams + * @brief namedtuple storing mutation parameters + * @ingroup pymod_pyrpn */ +/**@brief RPNMutationParams named tuple with mutation parameters */ +extern PyTypeObject rpn_mutation_params_SeqDesc; +/**@brief @ref rpn_mutation_params_SeqDesc named tuple description */ +extern PyStructSequence_Desc rpn_mutation_params_desc; + + +extern PyTypeObject ifs_mutation_weights_SeqDesc; +extern PyStructSequence_Desc ifs_mutation_weights_desc; + + +/**@brief Initialize mutation related types & stuff. Adds references + * to pyrpn module given in argument + * @param pyrpn_mod pyrpn python module instance + * @return 0 if no error else -1 */ +int pyrpn_python_mutation_init(PyObject *pyrpn_mod); + +/**@brief Try to convert a python object into a rpn_mutation_params_t + * @param py_params The python object + * @param params A pointer on the parameters to initialize + * @return -1 on failure and raise an exception + * @ingroup pymod_pyrpn_RPNExpr + */ +int pyrpn_pyobj_to_mutation_params(PyObject* py_params, rpn_mutation_params_t *params); + +int pyrpn_mutation_params_to_pyobj(const rpn_mutation_params_t *params, + PyObject **res); + + +int pyrpn_pyobj_to_ifs_mutation_weights(PyObject *py_params, + ifs_mutation_weights_t *w); + +int pyrpn_ifs_mut_weight_to_pyobj(const ifs_mutation_weights_t *w, + PyObject **mut_weights); + +/**@brief Set the mutation type weight from a PyObject* sequence + * @param w The IFS mutation weight structure to edit + * @param mut_type_weight Should be a sequence of 2 float values + * @return 0 if OK -1 on error (and PyErr_Occurred() == true) + */ +int pyrpn_ifs_mut_params_mut_type_weight(ifs_mutation_weights_t *w, + PyObject *mut_type_weight); + +/**@brief Set the mutation weight range from a PyObject* sequence + * @param w The IFS mutation weight structure to edit + * @param weight_range Should be a sequence of 2 float values + * @return 0 if OK -1 on error (and PyErr_Occurred() == true) + */ +int pyrpn_ifs_mut_params_weight_range(ifs_mutation_weights_t *w, + PyObject *weight_range); + +/**@brief Set the IF mutation weight from a PyObject* sequence + * @note Check that the sequence length correspond to the if_count + * in the ifs_mutation_weights_t struct + * @note If current w_mut_if field is NULL allocate it + * @param w The IFS mutation weight structure to edit + * @param weight_range Should be a sequence of if_count float values + * @param do_realloc If != 0 reallocate the w_mut_if field to correspond to + * if_count + * @return 0 if OK -1 on error (and PyErr_Occurred() == true) + */ +int pyrpn_ifs_mut_params_weight_mut_if(ifs_mutation_weights_t *w, + PyObject *mut_if_weight, short do_realloc); + +/**@brief Set the IF component weights from a PyObject* sequence + * @note The function cannot check the length validity of the given + * sequence (except that empty sequence are invalid). + * @param w The IFS mutation weight structure to edit + * @param mut_type_weight Should be a sequence of 2 float values + * @return 0 if OK -1 on error (and PyErr_Occurred() == true) + */ +int pyrpn_ifs_mut_params_if_comp_weight(ifs_mutation_weights_t *w, + PyObject *if_comp_weight); + +/**@brief Set the RPN mutation parameters from a PyObject* + * @param w The IFS mutation weight structure to edit + * @param mutation_params Can be a valid value for a RPNMutationParams + * or a sequence of valid values for a RPNMutationParams + * of len w->if_count + * @return 0 if OK -1 on error (and PyErr_Occurred() == true) + */ +int pyrpn_ifs_mut_params_mutation_params(ifs_mutation_weights_t *w, + PyObject *mutation_params); + +#endif + diff --git a/python_pyrpn.c b/python_pyrpn.c index 69dbc9f..7b36b29 100644 --- a/python_pyrpn.c +++ b/python_pyrpn.c @@ -21,15 +21,21 @@ /**@file python_pyrpn.c * @brief Python module & type definition * @ingroup python_ext + * @ingroup python_pyrpn * * This file contains pyrpn Python module definition */ -PyMethodDef rpnmodule_methods[] = { - {"get_ops", (PyCFunction)pyrpn_ops, METH_NOARGS, - "Returns a valid operands dict"}, - {"random_expr", (PyCFunction)pyrpn_random, METH_VARARGS | METH_KEYWORDS, - "Return a random RPN expression"}, +/**@brief pyrpn module's method definition */ +static PyMethodDef rpnmodule_methods[] = { + PYRPN_method("get_ops", pyrpn_ops, + METH_NOARGS, + "/", + "Return a dict with valid operands"), + PYRPN_method("random_expr", pyrpn_random, + METH_VARARGS | METH_KEYWORDS, + "args_count, token_count", + "Return a random RPN expression string"), {NULL} // Sentinel }; @@ -45,31 +51,150 @@ PyModuleDef rpnmodule = { NULL // m_free }; +PyObject *mmap_module = NULL; +PyObject *mmap_cls = NULL; + PyMODINIT_FUNC PyInit_pyrpn(void) { + if(mmap_module == NULL) + { + mmap_module = PyImport_ImportModule("mmap"); + if(PyErr_Occurred()) { return NULL; } + else if(!mmap_module) + { + PyErr_Format(PyExc_ImportError, "Unable to import mmap module"); + return NULL; + } + } + if(mmap_cls == NULL) + { + mmap_cls = PyObject_GetAttrString(mmap_module, "mmap"); + if(PyErr_Occurred()) { return NULL; } + else if (!mmap_cls) + { + PyErr_Format(PyExc_ImportError, "Unable to import mmap.mmap"); + return NULL; + } + } + - PyObject *mod; + PyObject *mod, *const_mod, *tokens_mod; // init module & globals mod = PyModule_Create(&rpnmodule); if(mod == NULL) { return NULL; } + //init constants module + const_mod = rpnconst_init(); + if(const_mod == NULL) + { + goto fail_init; + } + Py_INCREF(const_mod); + if(PyModule_AddObject(mod, "const", const_mod) < 0) + { + goto fail_add_const; + } + + //init tokens module + tokens_mod = rpntokens_module_init(); + if(tokens_mod == NULL) + { + goto fail_add_const; + } + Py_INCREF(tokens_mod); + if(PyModule_AddObject(mod, "tokens", tokens_mod) < 0) + { + goto fail_add_tokens; + } + // Init RPNExpr type if(PyType_Ready(&RPNExprType) < 0) { - return NULL; + goto fail_expr_type_ready; } // Add type to module Py_INCREF(&RPNExprType); if(PyModule_AddObject(mod, "RPNExpr", (PyObject*)&RPNExprType) < 0) { - Py_DECREF(&RPNExprType); - Py_DECREF(mod); - return NULL; + goto fail_add_rpnexpr; + } + + // Init RPNIterExpr type + if(PyType_Ready(&RPNIterExprType) < 0) + { + goto fail_iter_type_ready; + } + + Py_INCREF(&RPNIterExprType); + if(PyModule_AddObject(mod, "RPNIterExpr", (PyObject*)&RPNIterExprType) < 0) + { + goto fail_add_iter; + } + + if(PyType_Ready(&RPNIFSType) < 0) + { + goto fail_ifs_type_ready; + } + + Py_INCREF(&RPNIFSType); + if(PyModule_AddObject(mod, "RPNIFS", (PyObject*)&RPNIFSType) < 0) + { + goto fail_add_ifs; + } + + // 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) + { + goto fail_add_iter_expr_params; + } + + // Named tuple for token types + PyStructSequence_InitType(&rpn_token_types_SeqDesc, + &rpn_token_types_desc); + Py_INCREF(&rpn_token_types_SeqDesc); + if(PyModule_AddObject(mod, "RPNTokenTypesTuple", + (PyObject*)&rpn_token_types_SeqDesc) < 0) + { + goto fail_add_token_types_tuple; + } + + if(pyrpn_python_mutation_init(mod) < 0) + { + goto fail_init_mutation; } return mod; + +fail_init_mutation: + Py_DECREF(&rpn_token_types_SeqDesc); +fail_add_token_types_tuple: + Py_DECREF(&rpnif_params_SeqDesc); +fail_add_iter_expr_params: +fail_add_ifs: + Py_DECREF(&RPNIFSType); +fail_ifs_type_ready: +fail_add_iter: + Py_DECREF(&RPNIterExprType); +fail_iter_type_ready: +fail_add_rpnexpr: + Py_DECREF(&RPNExprType); +fail_expr_type_ready: +fail_add_tokens: + Py_DECREF(tokens_mod); +fail_add_const: + Py_DECREF(const_mod); +fail_init: + Py_DECREF(mod); + return NULL; } PyObject* pyrpn_ops(PyObject* mod, PyObject* noargs) @@ -114,7 +239,7 @@ PyObject* pyrpn_random(PyObject *mod, PyObject *args, PyObject *kwds) return NULL; } - expr = rpn_random(expr_sz, args_count); + expr = expr_sz?rpn_random(expr_sz, args_count):""; if(!expr) { snprintf(err_str, 128, @@ -124,6 +249,5 @@ PyObject* pyrpn_random(PyObject *mod, PyObject *args, PyObject *kwds) return NULL; } res = Py_BuildValue("s", expr); - //free(expr); return res; } diff --git a/python_pyrpn.h b/python_pyrpn.h index f69fe9f..b5e7d1d 100644 --- a/python_pyrpn.h +++ b/python_pyrpn.h @@ -28,10 +28,26 @@ #include "structmember.h" #include "rpn_jit.h" +#include "python_const.h" +#include "python_rpntoken.h" #include "python_rpnexpr.h" +#include "python_if.h" +#include "python_ifs.h" +#include "python_mutation.h" -/**@defgroup python_ext Python API - * @brief Python API definitions +/**@file python_pyrpn.h + * @brief Python module & type headers + * @ingroup python_ext + * @ingroup pymod_pyrpn + * + * This file is the header of the pyrpn Python module definition + */ + +/**@defgroup python_ext Python extension + * @brief RPNIFS python extension + * + * RPNIFS comes with a python extension module allowing to use the RPN + * expressions in python. * * @ref python_pyrpn.c and @ref python_rpnexpr.c should be compiled in a .so file * in order to expose a pyrpn Python module. @@ -46,35 +62,33 @@ * - pyrpn.RPNExpr.reset_stack(self) to set all stack items to 0 */ -/**@defgroup python_module pyrpn Python module - * @brief Exposed Python module : pyrpn +/**@defgroup pymod_pyrpn pyrpn Python module + * @brief Python extension main entry-point * @ingroup python_ext */ -/**@file python_pyrpn.h - * @brief Python module & type headers - * @ingroup python_module - * - * This file is the header of the pyrpn Python module definition - */ - +/**@brief RPNExpr Python class type definition */ extern PyTypeObject RPNExprType; /**@brief Python module initialization function - * @ingroup python_module */ + * @return The initialized module + * @ingroup pymod_pyrpn */ PyMODINIT_FUNC PyInit_pyrpn(void); -/**@brief pyrpn module methods list - * @ingroup python_module */ -extern PyMethodDef rpnmodule_methods[]; + /**@brief Python module specs - * @ingroup python_module */ + * @ingroup pymod_pyrpn */ extern PyModuleDef rpnmodule; +/**@brief Points on Python's std mmap module */ +extern PyObject *mmap_module; +/**@brief Python's mmap.mmap class */ +extern PyObject *mmap_cls; + /**@brief Return a dict with valid operations (short keys and long values) * @param mod pyrpn module object * @param noargs Dummy argument for METH_NOARG * @return The a new dict - * @ingroup python_module + * @ingroup pymod_pyrpn */ PyObject* pyrpn_ops(PyObject* mod, PyObject* noargs); @@ -82,7 +96,8 @@ PyObject* pyrpn_ops(PyObject* mod, PyObject* noargs); * @param mod pyrpn module object * @param args Position arguments in Python list * @param kwds Keywords arguments in Python dict - * @ingroup python_module + * @return A str instance + * @ingroup pymod_pyrpn */ PyObject* pyrpn_random(PyObject *mod, PyObject *args, PyObject *kwds); diff --git a/python_rpnexpr.c b/python_rpnexpr.c index 804cb44..127e934 100644 --- a/python_rpnexpr.c +++ b/python_rpnexpr.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Weber Yann + * Copyright (C) 2020,2023 Weber Yann * * This file is part of pyrpn. * @@ -18,62 +18,89 @@ */ #include "python_rpnexpr.h" -PyMethodDef RPNExpr_methods[] = { - {"eval", (PyCFunction)rpnexpr_eval, METH_FASTCALL, "Evaluate an expression"}, - {"reset_stack", (PyCFunction)rpnexpr_reset_stack, METH_NOARGS, - "Reset stack memory storage (set all items to 0)"}, - {"__getstate__", (PyCFunction)rpnexpr_getstate, METH_NOARGS, - "Pickling method. Return a bytes repr of tokenized expression \ -and the stack state."}, - {"__setstate__", (PyCFunction)rpnexpr_setstate, METH_O, - "Unpickling method"}, +/**@file python_rpnexpr.c + * @brief Python module & type definition + * @ingroup python_ext + * @ingroup python_pyrpn_RPNExpr + */ + +/**@brief @ref pymod_pyrpn_RPNExpr methods definition + * @ingroup python_pyrpn_RPNExpr */ +static PyMethodDef RPNExpr_methods[] = { + PYRPN_method("random", rpnexpr_random, + METH_CLASS | METH_VARARGS | METH_KEYWORDS, + "cls, args_count, token_count=10", + "Return a new random RPN expression string"), + PYRPN_method("default_mutation_params", rpnexpr_default_mutation_params, + METH_CLASS | METH_FASTCALL, + "cls, /", + "Return the default mutation parameters"), + PYRPN_method("eval", rpnexpr_eval, METH_FASTCALL, + "self, /, *args", + "Evaluate an expression"), + PYRPN_method("mutate", rpnexpr_mutate, + METH_VARARGS | METH_KEYWORDS, + "self, n_mutations=1, params=None", + "Mutate an expression"), + PYRPN_method("reset_stack", rpnexpr_reset_stack, + METH_NOARGS, + "self, /", + "Reset the stack (set all items to 0)"), + PYRPN_method("__getstate__", rpnexpr_getstate, + METH_NOARGS, + "self, /", + "Pickling method (see pickle module).\n" + "Return a bytes representation of the expression state."), + PYRPN_method("__setstate__", rpnexpr_setstate, + METH_O, + "self, state, /", + "Unpickling method (see pickle module)."), + PYRPN_method("__copy__", rpnexpr_copy, + METH_NOARGS, + "self, /", + "Return a new equivalent instance (see copy module)"), + PYRPN_method("uid", rpnexpr_getexprstate, + METH_NOARGS, + "self, /", + "Return a base64 uid for expression"), {NULL} //Sentinel }; -PyMemberDef RPNExpr_members[] = { +/**@brief @ref pymod_pyrpn_RPNExpr members definition + * @ingroup python_pyrpn_RPNExpr */ +static PyMemberDef RPNExpr_members[] = { {NULL} }; +/**@brief @ref pymod_pyrpn_RPNExpr methods definition + * @todo Continue sequence implementation with contains, concat, repeat etc. + * @ingroup python_pyrpn_RPNExpr */ +static PySequenceMethods RPNExpr_seq_methods = { + .sq_length = rpnexpr_len, + .sq_item = rpnexpr_token_item, + .sq_ass_item = rpnexpr_token_ass_item, +}; + + PyTypeObject RPNExprType = { PyVarObject_HEAD_INIT(NULL, 0) - "pyrpn.RPNExpr", /* tp_name */ - sizeof(PyRPNExpr_t), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)rpnexpr_del, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_reserved */ - rpnexpr_repr, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - rpnexpr_str, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_BASETYPE, /* tp_flags */ - "RPN expression evaluator", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - RPNExpr_methods, /* tp_methods */ - RPNExpr_members, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - rpnexpr_init, /* tp_init */ - 0, /* tp_alloc */ - rpnexpr_new, /* tp_new */ + .tp_name = "pyrpn.RPNExpr", + .tp_doc = "RPN expression evaluator\n\ +\n\ +Instanciation :\n\ +RPNExpr(expression:str, args_count:int, stack_size:int=16)", + .tp_basicsize = sizeof(PyRPNExpr_t), + .tp_itemsize = 0, + .tp_del = rpnexpr_del, + .tp_repr = rpnexpr_repr, + .tp_str = rpnexpr_str, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_richcompare = rpnexpr_richcompare, + .tp_methods = RPNExpr_methods, + .tp_as_sequence = &RPNExpr_seq_methods, + .tp_members = RPNExpr_members, + .tp_init = rpnexpr_init, + .tp_new = rpnexpr_new, }; PyObject* rpnexpr_new(PyTypeObject *subtype, PyObject *args, PyObject* kwds) @@ -89,6 +116,7 @@ PyObject* rpnexpr_new(PyTypeObject *subtype, PyObject *args, PyObject* kwds) expr = (PyRPNExpr_t*)ret; expr->rpn = NULL; expr->args = NULL; + expr->borrowed_expr = 0; return ret; } @@ -111,13 +139,6 @@ int rpnexpr_init(PyObject *self, PyObject *args, PyObject *kwds) return -1; } - if(strlen(expr) == 0) - { - PyErr_SetString(PyExc_ValueError, - "RpnExpr.__init__() expect expression argument to be not empty"); - return -1; - } - if(args_count < 0 || args_count > 255) { snprintf(err_str, 128, @@ -172,12 +193,44 @@ int rpnexpr_init(PyObject *self, PyObject *args, PyObject *kwds) return 0; } +PyObject* rpnexpr_init_borrowing(rpn_expr_t *borrowed) +{ + PyObject *args, *ret; + PyRPNExpr_t *instance; + + args = Py_BuildValue("sLL", "", borrowed->args_count, + borrowed->stack_sz); + if(!args || PyErr_Occurred()) + { + return NULL; + } + + ret = PyObject_CallObject((PyObject*)&RPNExprType, args); + if(!ret || PyErr_Occurred()) + { + Py_DECREF(args); + return NULL; + } + + Py_DECREF(args); + + instance = (PyRPNExpr_t*)ret; + + rpn_expr_close(instance->rpn); + free(instance->rpn); + + instance->borrowed_expr = 1; + instance->rpn = borrowed; + + return ret; +} + void rpnexpr_del(PyObject *self) { PyRPNExpr_t *expr_self; expr_self = (PyRPNExpr_t*)self; - if(expr_self->rpn) + if(expr_self->rpn && !expr_self->borrowed_expr) { rpn_expr_close(expr_self->rpn); free(expr_self->rpn); @@ -191,10 +244,16 @@ void rpnexpr_del(PyObject *self) } +PyObject* rpnexpr_getexprstate(PyObject *self, PyObject *noargs) +{ + /**@todo check if usefull */ + PyErr_SetString(PyExc_NotImplementedError, "Not implemented..."); + return NULL; +} + PyObject* rpnexpr_getstate(PyObject *self, PyObject *noargs) { - PyObject *res, *part; - PyRPNExpr_state_t resbuf; + PyObject *res; PyRPNExpr_t *expr_self; size_t total_sz; char err_str[128]; @@ -210,62 +269,26 @@ PyObject* rpnexpr_getstate(PyObject *self, PyObject *noargs) return NULL; } - total_sz = sizeof(PyRPNExpr_state_t); - total_sz += expr_self->rpn->toks.tokens_sz * sizeof(rpn_token_t); - total_sz += expr_self->rpn->stack_sz * sizeof(rpn_value_t); + total_sz = rpn_expr_serialize(expr_self->rpn, NULL, 0); - resbuf.total_sz = total_sz; - resbuf.argc = expr_self->rpn->args_count; - resbuf.stack_sz = expr_self->rpn->stack_sz; - resbuf.token_sz = expr_self->rpn->toks.tokens_sz; + char serial[total_sz]; - if(!(res = PyBytes_FromStringAndSize((char*)&resbuf, sizeof(resbuf)))) + rpn_expr_serialize(expr_self->rpn, serial, total_sz); + if(!(res = PyBytes_FromStringAndSize(serial, total_sz))) { return NULL; } - if(resbuf.stack_sz) - { - if(!(part=PyBytes_FromStringAndSize( - (char*)expr_self->rpn->stack, - sizeof(rpn_value_t) * resbuf.stack_sz))) - { - return NULL; - } - PyBytes_ConcatAndDel(&res, part); - if(!res) - { - return NULL; - } - } - if(resbuf.token_sz) - { - if(!(part=PyBytes_FromStringAndSize( - (char*)expr_self->rpn->toks.tokens, - sizeof(rpn_token_t) * resbuf.token_sz))) - { - return NULL; - } - PyBytes_ConcatAndDel(&res, part); - if(!res) - { - return NULL; - } - } return res; } PyObject* rpnexpr_setstate(PyObject *self, PyObject *state_bytes) { PyObject *tmp, *tmp2; - PyRPNExpr_state_t *state; PyRPNExpr_t *expr_self; - rpn_value_t *stack; - rpn_tokenized_t toks; const char *data; - size_t bsize, csize; + size_t bsize; int err; char err_str[256]; - size_t i; expr_self = (PyRPNExpr_t*)self; @@ -296,7 +319,7 @@ generating exception message !"); if(expr_self->rpn || expr_self->args) /* checking instance state */ { PyErr_SetString(PyExc_ValueError, - "RPNExpr.__getstate__() instance in bad state : \ + "RPNExpr.__setstate__() instance in bad state : \ should not be initialized"); return NULL; } @@ -304,37 +327,14 @@ should not be initialized"); /* Checking data size */ bsize = PyBytes_GET_SIZE(state_bytes); data = PyBytes_AS_STRING(state_bytes); - state = (PyRPNExpr_state_t*)data; if(bsize < sizeof(size_t)) { PyErr_SetString(PyExc_ValueError, "Invalid argument"); - } - if(bsize != state->total_sz) - { - snprintf(err_str, 128, - "RPNExpr.__setstate__() error : expected state to \ -contains %ld bytes but %ld found", - state->total_sz, bsize); - PyErr_SetString(PyExc_ValueError, err_str); - } - - /* Checking data "integrity" */ - csize = sizeof(PyRPNExpr_state_t) + - state->token_sz * sizeof(rpn_token_t) + - state->stack_sz * sizeof(rpn_value_t); - if(state->total_sz != csize) - { - snprintf(err_str, 128, - "RPNExpr.__setstate__() error : should have %ld as \ -total size but %ld found", - csize, state->total_sz); - PyErr_SetString(PyExc_ValueError, err_str); return NULL; } - - /* Alloc and init rpn expression & args buffer*/ + //alloc & init if(!(expr_self->rpn = malloc(sizeof(rpn_expr_t)))) { err = errno; @@ -346,7 +346,16 @@ rpn_expr_t : %s", return NULL; } bzero(expr_self->rpn, sizeof(rpn_expr_t)); - if(!(expr_self->args = malloc(sizeof(rpn_value_t)*state->argc))) + + if(rpn_expr_deserialize(expr_self->rpn, data, bsize) < 0) + { + PyErr_Format(PyExc_ValueError, + "RPNExpr.__setstate__() fails to deserialize (%s)) %s", + strerror(errno), expr_self->rpn->err_reason); + return NULL; + } + + if(!(expr_self->args = malloc(sizeof(rpn_value_t)*expr_self->rpn->args_count))) { err = errno; snprintf(err_str, 128, @@ -357,59 +366,138 @@ args buffer : %s", return NULL; } - if(rpn_expr_init(expr_self->rpn, state->stack_sz, state->argc) < 0) - { - snprintf(err_str, 256, - "RPNExpr.__setstate__() failed to init RPN expression \ -: %s", - expr_self->rpn->err_reason); - PyErr_SetString(PyExc_RuntimeError, err_str); - goto free_err; - } - - /* restore stack & untokenize expression */ - stack = (rpn_value_t*)(state+1); - memcpy(expr_self->rpn->stack, stack, - sizeof(rpn_value_t)*state->stack_sz); - - toks.argc = state->argc; - toks.tokens_sz = state->token_sz; - toks.tokens = malloc(sizeof(rpn_token_t)*state->token_sz); - if(!toks.tokens) - { - snprintf(err_str, 128, - "RPNExpr.__setstate__() failed to allocate tokens \ -: %s", - strerror(errno)); - PyErr_SetString(PyExc_RuntimeError, err_str); - goto close_err; - } - memcpy(toks.tokens, (rpn_token_t*)(stack+state->stack_sz), - sizeof(rpn_token_t)*state->token_sz); - - for(i=0; irpn, &toks, 0) < 0) - { - snprintf(err_str, 256, - "RPNExpr.__setstate__() unable to untokenize : %s", - expr_self->rpn->err_reason); - PyErr_SetString(PyExc_RuntimeError, err_str); - goto close_err; - } - Py_RETURN_NONE; - close_err: - rpn_expr_close(expr_self->rpn); - free_err: - return NULL; } + +PyObject* rpnexpr_copy(PyObject *self, PyObject *noargs) +{ + PyRPNExpr_t *copy; + PyObject *ret, *state, *setret; + + ret = PyObject_CallMethod((PyObject*)&RPNExprType, "__new__", "O", &RPNExprType); + copy = (PyRPNExpr_t*)ret; + + state = PyObject_CallMethod(self, "__getstate__", NULL); + if(PyErr_Occurred()) { + goto err; + } + setret = PyObject_CallMethod(ret, "__setstate__", "O", state); + if(PyErr_Occurred()) { + Py_DECREF(state); + goto err; + } + + Py_DECREF(state); + Py_DECREF(setret); + + return ret; +err: + PyObject_Del(copy); + return NULL; +} + + +Py_ssize_t rpnexpr_len(PyObject *self) +{ + PyRPNExpr_t *expr_self = (PyRPNExpr_t*)self; + return (Py_ssize_t)expr_self->rpn->toks.tokens_sz; +} + + +PyObject* rpnexpr_token_item(PyObject *self, Py_ssize_t idx) +{ + PyRPNExpr_t *expr_self = (PyRPNExpr_t*)self; + Py_ssize_t _idx = idx; + if(idx < 0) + { + idx = expr_self->rpn->toks.tokens_sz - 1 + idx; + } + if(idx < 0 || (size_t)idx >= expr_self->rpn->toks.tokens_sz) + { + PyErr_Format(PyExc_IndexError, + "No token %ld in expression of size %ld", + _idx, expr_self->rpn->toks.tokens_sz); + return NULL; + + } + return rpntoken_from_token(&expr_self->rpn->toks.tokens[idx]); +} + + +int rpnexpr_token_ass_item(PyObject *self, Py_ssize_t idx, PyObject* elt) +{ + PyRPNExpr_t *expr_self = (PyRPNExpr_t*)self; + Py_ssize_t _idx = idx; + if(idx < 0) + { + idx = expr_self->rpn->toks.tokens_sz - 1 + idx; + } + if(idx < 0 || (size_t)idx > expr_self->rpn->toks.tokens_sz) + { + PyErr_Format(PyExc_IndexError, + "Cannot set token %ld in expression of size %ld", + _idx, expr_self->rpn->toks.tokens_sz); + return -1; + } + + if(!PyObject_IsInstance(elt, (PyObject*)&RPNTokenType)) + { + PyErr_Format(PyExc_TypeError, + "Given element in not RPNToken subtype"); + return -1; + } + + short new_elt = 0; + if((size_t)idx == expr_self->rpn->toks.tokens_sz) + { + new_elt = 1; + expr_self->rpn->toks.tokens_sz++; + size_t new_sz = expr_self->rpn->toks.tokens_sz*sizeof(rpn_token_t); + rpn_token_t *tmp = realloc(expr_self->rpn->toks.tokens, new_sz); + if(!tmp) + { + PyErr_Format(PyExc_MemoryError, + "Error reallocation tokenized expression : %s", + strerror(errno)); + return -1; + } + expr_self->rpn->toks.tokens = tmp; + } + + rpn_token_t original = expr_self->rpn->toks.tokens[idx]; + + RPNToken_t *token = (RPNToken_t*)elt; + expr_self->rpn->toks.tokens[idx] = token->value; + if(rpn_expr_tokens_updated(expr_self->rpn) < 0) + { + PyErr_Format(PyExc_ValueError, + "Unable to update expression : %s", + strerror(errno)); + goto rollback; + } + return 0; + +rollback: + if(new_elt) + { + expr_self->rpn->toks.tokens_sz--; + } + else + { + expr_self->rpn->toks.tokens[idx] = original; + if(rpn_expr_tokens_updated(expr_self->rpn) < 0) + { + PyErr_Format(PyExc_RuntimeError, + "Unable to rollback expression : %s", + strerror(errno)); + goto rollback; + } + } + return -1; +} + + PyObject* rpnexpr_eval(PyObject* self, PyObject** argv, Py_ssize_t argc) { PyRPNExpr_t *expr_self; @@ -451,11 +539,57 @@ PyObject* rpnexpr_eval(PyObject* self, PyObject** argv, Py_ssize_t argc) } res = rpn_expr_eval(expr_self->rpn, expr_self->args); -//dprintf(2, "[RES=%lu]\n", res); return PyLong_FromUnsignedLong(res); } + +PyObject* rpnexpr_mutate(PyObject* slf, PyObject *args, PyObject *kwds) +{ + PyRPNExpr_t *self = (PyRPNExpr_t*)slf; + + char *str_args = "|IO:RPNExpr.mutate"; + char *names[] = {"n_mutations", "params", NULL}; + + PyObject *py_params = NULL; + unsigned int n_mutations = 1; + rpn_mutation_params_t params; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, str_args, names, + &n_mutations, &py_params)) + { + return NULL; + } + + if(!py_params || py_params == Py_None) + { + if(py_params == Py_None) { Py_DECREF(Py_None); } + memcpy(¶ms, &rpn_mutation_params_default, + sizeof(rpn_mutation_params_t)); + } + else + { + if(pyrpn_pyobj_to_mutation_params(py_params, ¶ms) < 0) + { + PyErr_SetString(PyExc_ValueError, "Bad value for params arguments"); + return NULL; + } + } + + for(size_t i=0; irpn->toks), ¶ms) < 0) + { + PyErr_Format(PyExc_RuntimeError, "Mutation failed : %s", + strerror(errno)); + return NULL; + } + } + + rpn_expr_tokens_updated(self->rpn); + Py_RETURN_NONE; +} + PyObject* rpnexpr_reset_stack(PyObject *self, PyObject *noargs) { rpn_expr_reset_stack(((PyRPNExpr_t*)self)->rpn); @@ -500,3 +634,105 @@ PyObject* rpnexpr_repr(PyObject *self) return res; } + +PyObject* rpnexpr_richcompare(PyObject *_self, PyObject *other, int op) +{ + PyObject *sta, *stb, *res; + + if(!PyObject_IsInstance(other, (PyObject*)&RPNExprType)) + { + PyErr_Format(PyExc_TypeError, + "Can only be compared with RPNExpr"); + return NULL; + } + + sta = rpnexpr_getstate(_self, NULL); + stb = rpnexpr_getstate(other, NULL); + + res = PyObject_RichCompare(sta, stb, op); + + Py_DECREF(sta); + Py_DECREF(stb); + + return res; +} + + +PyObject* rpnexpr_random(PyObject *cls, PyObject *args, PyObject *kwds) +{ + long long int args_count, expr_sz; + char *expr, err_str[128]; + PyObject *res; + char *names[] = {"args_count", "token_count", NULL}; + + expr_sz = 10; + if(!PyArg_ParseTupleAndKeywords(args, kwds, "L|L:pyrpn.random_expr", names, + &args_count, &expr_sz)) + { + return NULL; + } + + expr = rpn_random(expr_sz, args_count); + if(!expr) + { + snprintf(err_str, 128, + "Error generating random expression : %s", + strerror(errno)); + PyErr_SetString(PyExc_RuntimeError, err_str); + return NULL; + } + res = Py_BuildValue("s", expr); + //free(expr); + return res; +} + +PyObject* rpnexpr_default_mutation_params(PyObject *cls, PyObject **argv, Py_ssize_t argc) +{ + PyObject *res, *wtypes; + + if(!(wtypes = PyStructSequence_New(&rpn_token_types_SeqDesc))) + { + return NULL; + } + + if(!(res = PyStructSequence_New(&rpn_mutation_params_SeqDesc))) + { + return NULL; + } + + // wtypes filled with 1.0 + PyObject *one = PyFloat_FromDouble(1.0); + for(size_t i=0; i<3; i++) + { + PyStructSequence_SET_ITEM(wtypes, i, one); + } + + + // max_len + PyStructSequence_SET_ITEM(res, 0, + PyLong_FromLong(rpn_mutation_params_default.min_len)); + // weight_add + PyStructSequence_SET_ITEM(res, 1, + PyFloat_FromDouble(rpn_mutation_params_default.w_add)); + // weight_del + PyStructSequence_SET_ITEM(res, 2, + PyFloat_FromDouble(rpn_mutation_params_default.w_del)); + // weight_mut + PyStructSequence_SET_ITEM(res, 3, + PyFloat_FromDouble(rpn_mutation_params_default.w_mut)); + // weight_mut_soft + PyStructSequence_SET_ITEM(res, 4, + PyFloat_FromDouble(rpn_mutation_params_default.w_mut_soft)); + + /** TODO use rpn_mutation_params_default instead of wtypes [1,1,1] */ + // weight_add_elt + PyStructSequence_SET_ITEM(res, 5, wtypes); + Py_INCREF(wtypes); + // weight_mut_elt + PyStructSequence_SET_ITEM(res, 6, wtypes); + Py_INCREF(wtypes); + + return res; + +} + diff --git a/python_rpnexpr.h b/python_rpnexpr.h index e82af95..88aa1df 100644 --- a/python_rpnexpr.h +++ b/python_rpnexpr.h @@ -16,45 +16,46 @@ * You should have received a copy of the GNU General Public License * along with pyrpn. If not, see . */ -#ifndef _PYTHON_PYRPN_H__ -#define _PYTHON_PYRPN_H__ +#ifndef _PYTHON_RPNEXPR_H__ +#define _PYTHON_RPNEXPR_H__ #include "config.h" #include +#include #define PY_SSIZE_T_CLEAN #include #include "structmember.h" #include "rpn_jit.h" - -/**@defgroup python_type RPNExpr Python class - * @brief Exposed Python class : RPNExpr - * @ingroup python_module - */ +#include "python_rpntoken.h" +#include "python_mutation.h" +#include "python_const.h" /**@file python_rpnexpr.h - * @brief Python RPNExpr type headers - * @ingroup python_type + * @brief pyrpn.RPNExpr definition + * @ingroup python_ext + * @ingroup pymod_pyrpn_RPNExpr * * This file is the header of the RPNExpr Python class */ -/**@brief RPNExpr Python class methods list - * @ingroup python_type */ -extern PyMethodDef RPNExpr_methods[]; -/**@brief RPNExpr Python class members list - * @ingroup python_type */ -extern PyMemberDef RPNExpr_members[]; +/**@defgroup pymod_pyrpn_RPNExpr pyrpn.RPNExpr + * @brief Exposed Python class : RPNExpr + * @ingroup pymod_pyrpn + */ + + /**@brief RPNExpr Python class type definition - * @ingroup python_type */ + * @ingroup pymod_pyrpn_RPNExpr */ extern PyTypeObject RPNExprType; /**@brief Structure holding RPNExpr objects - * @ingroup python_type */ + * @ingroup pymod_pyrpn_RPNExpr */ typedef struct { + /** Python's type definition */ PyObject_VAR_HEAD; /**@brief Pointer on @ref rpn_expr_s */ @@ -63,24 +64,43 @@ typedef struct /**@brief Array storing expression argument * @note As attribute of rpn_expr allowing malloc & free only once */ rpn_value_t *args; + + /**@brief If true, someone else will take care of freeing the + * rpn expression at deletion */ + short borrowed_expr; } PyRPNExpr_t; /**@brief Organize PyRPNExpr_t state data * @see rpnexpr_getstate - * @see rpnexpr_setstate */ + * @see rpnexpr_setstate + * @ingroup pymod_pyrpn_RPNExpr */ typedef struct { + /**@brief The total size of the state */ size_t total_sz; + /**@brief Expression's argument count */ size_t argc; + /**@brief Expression's stack size */ unsigned char stack_sz; + /**@brief Number of tokens in expression */ size_t token_sz; } PyRPNExpr_state_t; +/**@brief Return a new instance of RPNExpr with a borrowed expression + * @param borrowed An rpn expression to borrow + * @note Borrowed expression means that the rpn_expr_t is handled by someone + * else and should not be freed when the instance is deleted + * @return A new RPNExpr instance + * @ingroup pymod_pyrpn_RPNExpr + */ +PyObject* rpnexpr_init_borrowing(rpn_expr_t *borrowed); + /**@brief RpnExpr __new__ method * @param subtype Type of object being created (pyrpn.RPNExpr) * @param args positional arguments for subtype - * @param kwargs keyword argumenrs for subtype + * @param kwds keyword argumenrs for subtype * @return The new Python RPNExpr object + * @ingroup pymod_pyrpn_RPNExpr */ PyObject* rpnexpr_new(PyTypeObject *subtype, PyObject *args, PyObject* kwds); @@ -89,61 +109,143 @@ PyObject* rpnexpr_new(PyTypeObject *subtype, PyObject *args, PyObject* kwds); * @param args Positional arguments list * @param kwds Keywords arguments dict * @return 0 if no error else -1 - * @ingroup python_type + * @ingroup pymod_pyrpn_RPNExpr */ int rpnexpr_init(PyObject *self, PyObject *args, PyObject *kwds); /**@brief RPNExpr __del__ method * @param self RPNExpr instance - * @ingroup python_type + * @ingroup pymod_pyrpn_RPNExpr */ void rpnexpr_del(PyObject *self); +/**@brief Returns a byte representation of expression (like getstate but + * without the stack informations + * @param self RPNExpr instance + * @param noargs Not an argument... + * @return A bytes Python instance + * @ingroup pymod_pyrpn_RPNExpr + */ +PyObject* rpnexpr_getexprstate(PyObject *self, PyObject *noargs); + /**@brief RPNExpr __getstate__ method for pickling - * @param cls RPNExpr type object + * @param self RPNExpr type object * @param noargs Not an argument... * @return A bytes Python instance suitable as argument for * @ref rpnexpr_setstate + * @ingroup pymod_pyrpn_RPNExpr */ -PyObject* rpnexpr_getstate(PyObject *cls, PyObject *noargs); +PyObject* rpnexpr_getstate(PyObject *self, PyObject *noargs); /**@brief RPNExpr __setstate__ method for pickling * @param cls RPNExpr type object * @param state Should by a bytes Python instance returned by @ref * rpnexpr_getstate * @return A bytes Python instance suitable as argument for - * @ref rpnexpr_setstate + * @ref rpnexpr_getstate + * @ingroup pymod_pyrpn_RPNExpr */ PyObject* rpnexpr_setstate(PyObject *cls, PyObject *state); +/**@brief RPNExpr __copy__ method for cloning + * @param cls RPNExpr class + * @param noargs Not an argument... + * @return A new cloned instance + * @ref rpnexpr_setstate + * @ingroup pymod_pyrpn_RPNExpr + */ +PyObject* rpnexpr_copy(PyObject *cls, PyObject *noargs); + +/**@brief RPNExpr.__len__() method + * @param self RPNExpr instance + * @return A integer with the number of tokens in expression + * @ingroup pymod_pyrpn_RPNExpr + */ +Py_ssize_t rpnexpr_len(PyObject *self); + +/**@brief Sequence method for __getitem__ token getter + * @param self RPNExpr instance + * @param idx The item index (can be negative) + * @return A @ref RPNTokenType subclass instance + * @ingroup pymod_pyrpn_RPNExpr + */ +PyObject* rpnexpr_token_item(PyObject *self, Py_ssize_t idx); + +/**@brief Sequence method for __setitem__ token setter + * @param self RPNExpr instance + * @param idx The item index + * @param elt An RPNTokenType subclass token to set + * @return 0 or -1 on error and raise IndexError + * @ingroup pymod_pyrpn_RPNExpr + */ +int rpnexpr_token_ass_item(PyObject *self, Py_ssize_t idx, PyObject* elt); + /**@brief Eval an RPN expression given arguments and return the * value * @param self RPNExpr instance * @param argv Array of PyObject* arguments * @param argc Number of arguments * @return The value resulting of evaluation - * @ingroup python_type + * @ingroup pymod_pyrpn_RPNExpr */ PyObject* rpnexpr_eval(PyObject* self, PyObject** argv, Py_ssize_t argc); +/**@brief Mutate an RPN expression given arguments and return the value + * @param self RPNExpr instance + * @param args Positionnal arguments + * @param kwds Keyword arguments + * @return Py_None + * @ingroup pymod_pyrpn_RPNExpr + */ +PyObject* rpnexpr_mutate(PyObject* self, PyObject *args, PyObject *kwds); + /**@brief Set all stack item to zero * @param self RPNExpr instance * @param noargs Dummy argument for METH_NOARG * @return None - * @ingroup python_type + * @ingroup pymod_pyrpn_RPNExpr */ PyObject* rpnexpr_reset_stack(PyObject *self, PyObject *noargs); /**@brief RPNExpr.__repr__() * @param self RPNExpr instance - * @ingroup python_type + * @return An str instance representing the exppression + * @ingroup pymod_pyrpn_RPNExpr */ PyObject* rpnexpr_repr(PyObject *self); /**@brief RPNExpr.__str__() * @param self RPNExpr instance - * @ingroup python_type + * @return An str instance representing the exppression + * @ingroup pymod_pyrpn_RPNExpr */ PyObject* rpnexpr_str(PyObject *self); +/**@brief RPNExpr PEP-207 richcompare method + * @param _self RPNExpr instance + * @param other RPNExpr instance to compare to + * @param op The comparison to be done + * @return Py_True or Py_False + * @ingroup pymod_pyrpn_RPNExpr + */ +PyObject* rpnexpr_richcompare(PyObject *_self, PyObject *other, int op); + +/**@brief Return a new Python str with a random RPN expression + * @param cls pyrpn module object + * @param args Position arguments in Python list + * @param kwds Keywords arguments in Python dict + * @return A str instance + * @ingroup pymod_pyrpn + */ +PyObject* rpnexpr_random(PyObject *cls, PyObject *args, PyObject *kwds); + +/**@brief Return a new named tuple containing default mutation parameters + * @param cls The class (class method) + * @param argv The arguments (FASTCALL) + * @param argc The number of arguments + * @return The named tuple + * @ingroup pymod_pyrpn_RPNExpr + */ +PyObject* rpnexpr_default_mutation_params(PyObject *cls, PyObject **argv, Py_ssize_t argc); + #endif diff --git a/python_rpnifs/__main__.py b/python_rpnifs/__main__.py new file mode 100755 index 0000000..488d9b6 --- /dev/null +++ b/python_rpnifs/__main__.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +import copy +import random +import multiprocessing + +try: + import pyrpn +except (ImportError, NameError) as e: + print("Error importing pyrpn. Try to run make.", + file=sys.stderr) + raise e + +from PIL import Image +from PIL.PngImagePlugin import PngImageFile, PngInfo +import numpy as np +from tqdm import tqdm + +from .ifs1 import IFS1 +from .ifs2 import IFS2 +from .ifs3 import IFS3 +from .ifs4 import IFS4 +from .ifs5 import IFS5 + +from .shape_score import ShapeScore + + +#steps = 1024**2 +steps=512**2 +#steps=1024**2 +#steps*=4 +#steps*=10 + +#pool_sz = 30 +#best_sz = 6 + +pool_sz = 32 +best_sz = 8 + +n_mutation = 10 +outfile='/tmp/rpnifs2_%02d.png' +outfile_grp='/tmp/rpnifs2_%02d_grp.png' +#world = IFS1.get_world() +#pool = [IFS1(init_sz=3, world=world) for _ in range(pool_sz)] +#world = IFS2.get_world() +#pool = [IFS2(init_sz=3, world=world) for _ in range(pool_sz)] +#world = IFS3.get_world() +#pool = [IFS3(init_sz=3, world=world) for _ in range(pool_sz)] +#world = IFS4.get_world() +#pool = [IFS4(init_sz=3, world=world) for _ in range(pool_sz)] +world = IFS5.get_world() +#pool = [IFS5(init_sz=3, world=world) for _ in range(pool_sz)] +pool = [IFS5(init_sz=6, world=world) for _ in range(pool_sz)] +print('POOL ready') + +def stepper(ifs): + for _ in range(steps): + ifs.step() + return ifs + +def get_score(world): + shapescore = ShapeScore(world, jobs=1) + score = shapescore.score() + return score, shapescore + +generation=0 +mp_pool = multiprocessing.Pool(2) +while True: + generation+=1 + print('='*78) + print(' '*10 + 'GENERATION #%d' % generation) + print('='*78) + + scores = [] + i=0 + #for ifs in pool: + #for ifs in tqdm(pool, unit='ifs'): + # ifs.raz_world() + # #for _ in tqdm(range(steps), total=steps, unit_scale=True): + # for _ in range(steps): + # ifs.step() + pool = [ifs for ifs in tqdm(mp_pool.imap(stepper, pool), + total=len(pool), + unit='ifs')] + + for i, (score,shapescore) in tqdm(enumerate(mp_pool.imap(get_score, + [ifs._world for ifs in pool])), + unit='ifs', total=len(pool)): + ifs = pool[i] + + #score = ifs.score() + scores.append((ifs, score)) + #print('%02d) %5.3f %s' % (i, score, ifs)) + print('%02d) %5.3f' % (i, score)) + im = Image.fromarray(ifs.get_image()) + im.save(outfile % i) + shapescore.result_image(outfile_grp % i) + + im_png = PngImageFile(outfile % i) + metadata = PngInfo() + metadata.add_text('Score', '%5.3f' % score) + metadata.add_text('Generation', '%04d' % generation) + metadata.add_text('Pool_position', '%02d' % i) + for key, text in ifs.expr_dict().items(): + metadata.add_text(key, text) + im_png.save(outfile % i, pnginfo=metadata) + + # Selecting best + scores = sorted(scores, key=lambda v: 0 if v[1] == np.nan else v[1], + reverse=True) + best = scores[:best_sz] + for i, (ifs, score) in enumerate(best): + print('%02d) %5.3f %s' % (i, score, ifs)) + + # Saving best + im = Image.fromarray(best[0][0].get_image()) + outfile_gen = '/tmp/gen_%03d.png' + im.save(outfile_gen % generation) + + im_png = PngImageFile(outfile_gen % generation) + metadata = PngInfo() + metadata.add_text('Score', '%5.3f' % score) + metadata.add_text('Generation', '%04d' % generation) + metadata.add_text('Pool_position', '%02d' % 1) + for key, text in ifs.expr_dict().items(): + metadata.add_text(key, text) + im_png.save(outfile_gen % generation, pnginfo=metadata) + + # Mutating best + pool = [b for b, _ in best] + for ifs, _ in best: + nmut_max = (pool_sz//best_sz)-1 + for nmut in range(nmut_max): + # needs >= IFS5 + again = 0 + while True: + # half random mutation and half mutation by adding two IFS + if again > 5 or nmut < nmut_max / 2: + new = copy.copy(ifs) + new.mutation(n_mutation) + else: + new = ifs + random.choice(best)[0] + + for cur in pool: + if cur - new == 0: + break + else: + pool.append(new) + break + again += 1 + ### + #new = copy.copy(ifs) + #new.mutation(n_mutation) + #pool.append(new) + diff --git a/python_rpnifs/expr.py b/python_rpnifs/expr.py new file mode 100644 index 0000000..c9b3441 --- /dev/null +++ b/python_rpnifs/expr.py @@ -0,0 +1,299 @@ +""" @namespace python_rpnifs.expr + @brief Handles expression generation & mutation +""" +import copy +import random + +import numpy + +import pyrpn + +class RpnSym(object): + """ @brief Abstract class representing RpnExpr symbols """ + + def __init__(self): + raise NotImplementedError('Abstract class') + + def __sub__(self, b): + """ @return the difference between two symbols : 2 if types differs, + 1 if same type but different value + """ + if type(b) != type(self): + return 2 + elif str(b) != str(self): + return 1 + return 0 + +class RpnOp(RpnSym): + """ @brief an RPN operation symbol """ + + def __init__(self, op=None): + """ @brief instanciate an RPN symbol + @param op if None a random op is choosen else a value from + pyrpn.get_ops().values() is expected + """ + if op is None: + op = self.random() + elif op not in pyrpn.get_ops().values(): + raise ValueError('Invalid operation %r' % op) + self.__op = op + + def __str__(self): + return self.__op + + def __copy__(self): + return type(self)(self.__op) + + def to_bytes(self): + """ @return a 9 bytes representation of the symbol """ + brepr = self.__op.encode('utf8') + return bytes([1] + ([0]*(8-len(brepr)))) + brepr + + @staticmethod + def random(): + """ @return a random RpnOp instance """ + return random.choice(list(pyrpn.get_ops().values())) + + +class RpnConst(RpnSym): + """ @brief an RPN numeric constant symbol """ + + def __init__(self, value=None): + """ @brief instanciate an RPN numeric constant symbol + @param value a 64bit unsigned integer (a random one is choosen + if None given) + """ + if value is None: + value = random.randint(0, (1<<64)-1) + elif value < 0 or value > (1<<64)-1: + raise ValueError('Outof bound value %d' % value) + self.__val = value + + def __copy__(self): + return type(self)(self.__val) + + def __str__(self): + return '0x%X' % self.__val + + def to_bytes(self): + """ @return a 9 bytes representation of the symbol """ + return bytes([0]) + self.__val.to_bytes(8, 'little') + + +class RpnVar(RpnSym): + """ @brief an RPN variable symbol + + Variables are indexed with integer, string representation are : A0, A1 + etc. + """ + + def __init__(self, varnum=None, nvar=1): + """ @brief instanciate an RPN variable symbol + @param varnum the variable number (random one choose if None given) + @param nvar the maximum variable number + """ + if varnum is None: + if nvar == 1: + varnum = 0 + else: + varnum = random.randint(0, nvar-1) + elif varnum < 0 or varnum >= nvar: + raise ValueError('Variable A%d do not exists' % varnum) + self.__val = varnum + self.__nvar = nvar + + @property + def nvar(self): + """ @return the maximum variable number """ + return self.__nvar + + def __copy__(self): + return type(self)(self.__val, self.__nvar) + + def __str__(self): + return 'A%d' % self.__val + + def to_bytes(self): + """ @return a 9 bytes representation of the symbol """ + brepr = str(self).encode('utf8') + return bytes([1] + ([0]*(8-len(brepr)))) + brepr + + +class RpnExpr(object): + """ @brief Wrapper on pyrpnifs.RPNExpr + + Allow using pyrpnifs.RPNExpr while keeping a high level representation + of the expression (using @ref RpnSym child classes). + """ + + def __init__(self, sz=0, w_op=4, w_const=1, w_var=2, nvar=1): + """ @param sz The expression size for random generation + @param w_op RpnOp weight on random generation + @param w_const RpnConst weight on random generation + @param w_var RpnVar weight on random generation + @param n_var the variable count + """ + self.sz = 0 + self._weight = (w_op, w_const, w_var) + choices = (RpnOp, RpnConst, lambda: RpnVar(nvar=nvar)) + self.expr = [random.choices(choices, self._weight)[0]() + for _ in range(sz)] + self.__nvar = nvar + if sz > 0: + self.refresh_expr() + + def refresh_expr(self): + """ @brief Refresh the internal pyrpn.RPNExpr using the + @ref RpnSym representation + """ + self.pyrpn = pyrpn.RPNExpr(str(self), self.__nvar) + + def __str__(self): + return ' '.join([str(elt) for elt in self.expr]) + + def __repr__(self): + return '' % (len(self.expr), str(self)) + + def __copy__(self): + w_op, w_const, w_var = self._weight + ret = RpnExpr(0, w_op, w_const, w_var, self.__nvar) + ret.expr = [copy.copy(elt) for elt in self.expr] + ret.refresh_expr() + return ret + + def to_bytes(self): + """ @return a bytes representation with 9 bytes for each symbols """ + return b''.join([elt.to_bytes() for elt in self.expr]) + + def __sub__(self, b): + return self.levenshtein(b) + + def eval(self, *args): + """ @brief Evaluate the expression with given arguments + @param args* an array of argument (with len = nvar) + """ + return self.pyrpn.eval(*args) + + def mutation(self, n=1, min_len=3, w_add=1.25, w_del=1, w_mut=2, + w_mut_soft=4, w_add_elt=(1,1,1), w_mut_elt=(1,1,1)): + """ @brief Expression mutation + @param n mutation count + @param min_len the minimum length for an expression + @param w_add weight of adding a symbol + @param w_del weight of deleting a symbol + @param w_mut weight of mutating a symbol + @param w_mut_soft weight of mutationg a symbol without type change + @param w_add_elt weights for each types (op, const, var) when + adding a symbol + @param w_mut_elt weight for each types (op, const, var) when + mutating a symbol + @note The internal pyrpn.RPNExpr is refreshed after mutations + """ + for _ in range(n): + if len(self.expr) <= min_len: + self.add(*w_add_elt) + return + funs = ((self.add, w_add_elt), + (self.delete, []), + (self.mutate, w_mut_elt), + (self.mutate_soft, [])) + choice = random.choices((0,1,2), + (w_add, w_del, w_mut))[0] + fun, args = funs[choice] + fun(*args) + self.refresh_expr() + pass + + def add(self, w_op=1, w_const=1, w_var=1): + """ @brief Mutate by adding a symbol """ + idx = random.randint(0,len(self.expr)) + new = random.choices((RpnOp, RpnConst, RpnVar), self._weight)[0] + new = new(nvar=self.__nvar) if new == RpnVar else new() + self.expr.insert(idx, new) + pass + + def delete(self, *args): + """ @brief Mutate by deleting a symbol """ + idx = random.randint(0, len(self.expr)-1) + self.expr.pop(idx) + pass + + def mutate(self, w_op=1, w_const=1, w_var=1): + """ @brief Mutate changing a symbol into another """ + idx = random.randint(0, len(self.expr)-1) + new = random.choices((RpnOp, RpnConst, RpnVar), self._weight)[0] + new = new(nvar=self.__nvar) if new == RpnVar else new() + self.expr[idx] = new + pass + + def mutate_soft(self): + """ @brief Mutate changing a symbol into another of the same type """ + idx = random.randint(0, len(self.expr)-1) + cur = self.expr[idx] + cls = type(cur) + if cls == RpnVar: + new = cls(nvar=cur.nvar) + else: + new = cls() + self.expr[idx] = new + pass + + def levenshtein(a, b, custom_weight=True): + """ @brief Calculate a levenshtein distance between two expression + @param a expression A + @param b expression B + @param custom_weight If True a different symbol of same type weight + 1 and a different symbol of different type weight 2. Else no + special weight is applied. + @return an integer + """ + + len_a = len(a.expr) + len_b = len(b.expr) + + dist = numpy.zeros((len_a+1, len_b+1)) + for i in range(len_a): + dist[i][0] = i + for i in range(len_b): + dist[0][i] = i + + for ia in range(len_a): + for ib in range(len_b): + diff = a.expr[ia] - b.expr[ib] # in (0,1,2) + if diff > 0: + val = min([dist[ia][ib], dist[ia+1][ib], dist[ia][ib+1]]) + dist[ia+1][ib+1] = val + (diff if custom_weight else 1) + else: + dist[ia+1][ib+1] = dist[ia][ib] + + return dist[len_a][len_b] + + @classmethod + def from_string(cls, expr, nvar=1): + """ @brief Return an RpnExpr instance given a string representing + an expression + @param epxr the string representation + @param nvar the variable count + """ + res = RpnExpr(sz=0) + + for spl in expr.split(' '): + spl = spl.strip() + if not spl: + continue + if spl[0] == 'A': + try: + argnum = int(spl[1:]) + except Exception: + raise ValueError('Invalid argument %r' % spl) + self.expr.append(RpnVar(argnum, nvar)) + elif spl in pyrpn.get_ops().values(): + self.expr.append(RpnOp(spl)) + else: + try: + val = int(spl) + except Exception: + raise ValueError('Invalid token %r' % spl) + self.expr.append(RpnVal(spl)) + return res + diff --git a/python_rpnifs/fractdim.py b/python_rpnifs/fractdim.py new file mode 100644 index 0000000..dad23dc --- /dev/null +++ b/python_rpnifs/fractdim.py @@ -0,0 +1,73 @@ +import sys +import numpy as np +import scipy +import scipy.misc + +def rgb2gray(rgb): + """ @return a grayscale version of an RGB "image" """ + r, g, b = rgb[:,:,0], rgb[:,:,1], rgb[:,:,2] + gray = 0.2989 * r + 0.5870 * g + 0.1140 * b + return gray + +def rgba2gray(rgba): + """ @return a grayscale version of an RGBA "image" """ + r, g, b, a = rgba[:,:,0], rgba[:,:,1], rgba[:,:,2], rgba[:,:,3] + gray = (0.2989 * r + 0.5870 * g + 0.1140 * b) * (a/255) + return gray + +def rgb2grayavg(rgb): + """ @return a grayscale version of an RGB "image" """ + r, g, b = rgb[:,:,0], rgb[:,:,1], rgb[:,:,2] + gray = (r+g+b)/3 + return gray + +def rgba2grayavg(rgba): + """ @return a grayscale version of an RGBA "image" """ + r, g, b, a = rgba[:,:,0], rgba[:,:,1], rgba[:,:,2], rgba[:,:,3] + gray = ((r + g + b)/3) * (a/255) + return gray + +def fractal_dimension(Z, threshold=None): + ''' @return Minkowski-Bouligand dimension (computed) ''' + # Only for 2d image + assert(len(Z.shape) == 2) + + # From https://github.com/rougier/numpy-100 (#87) + def boxcount(Z, k): + S = np.add.reduceat( + np.add.reduceat(Z, np.arange(0, Z.shape[0], k), axis=0), + np.arange(0, Z.shape[1], k), axis=1) + + # We count non-empty (0) and non-full boxes (k*k) + return len(np.where((S > 0) & (S < k*k))[0]) + + if threshold is None: + threshold = np.mean(Z) + if threshold < 0.2: + threshold = 0.2 + + # Transform Z into a binary array + Z = (Z < threshold) + + # Minimal dimension of image + p = min(Z.shape) + + # Greatest power of 2 less than or equal to p + n = 2**np.floor(np.log(p)/np.log(2)) + + # Extract the exponent + n = int(np.log(n)/np.log(2)) + + # Build successive box sizes (from 2**n down to 2**1) + sizes = 2**np.arange(n, 1, -1) + + # Actual box counting with decreasing size + counts = [] + for size in sizes: + counts.append(boxcount(Z, size)) + + # Fit the successive log(sizes) with log (counts) + coeffs = np.polyfit(np.log(sizes), np.log(counts), 1) + return 0 if np.isnan(-coeffs[0]) else -coeffs[0] + + diff --git a/python_rpnifs/ifs1.py b/python_rpnifs/ifs1.py new file mode 100644 index 0000000..806660c --- /dev/null +++ b/python_rpnifs/ifs1.py @@ -0,0 +1,132 @@ +import random +import time +import copy + +import numpy as np +from skimage.restoration import estimate_sigma + +from .expr import * +from .fractdim import * + + +class IFS1(object): + """ @brief 1 Variable IFS with R,G,B,A X,Y decomposition """ + + #height=width=1024 + height=width=512 + + def __init__(self, nexpr=4, init_sz=1, world=None): + self._nexpr = nexpr + self._expr = [RpnExpr(sz=init_sz, nvar=1) + for _ in range(nexpr)] + self._world = world + if self._world is None: + self._world = self.get_world() + self._position = [random.randint(0, self.height-1), + random.randint(0, self.width-1)] + + @classmethod + def get_world(cls): + return np.zeros((cls.height,cls.width,4), dtype=np.uint8) + + def raz_world(self): + for i in range(self.height): + for j in range(self.width): + self._world[i][j] = [0,0,0,0] + + def __str__(self): + return ";".join([str(e) for e in self._expr]) + + def __copy__(self): + ret = IFS1(nexpr=self._nexpr, world=self._world) + ret._expr = [copy.copy(e) for e in self._expr] + #ret._world = copy.deepcopy(self._world) + return ret + + def get_image(self): + return self._world + + def mutation(self, n=1, rand=True): + n = 1 if n <= 1 else random.randint(1,n) + for _ in range(n): + random.choice(self._expr).mutation() + + def step(self): + r,g,b,a = [int(e) + for e in self._world[self._position[0]][self._position[1]]] + arg = r + arg <<= 8 + arg += g + arg <<= 8 + arg += b + arg <<= 8 + arg += a + arg <<=16 + arg += self._position[0] + arg <<=16 + arg += self._position[1] + + ret = int(random.choice(self._expr).eval(arg)) + dr, dg, db, da = r,g,b,a + + self._position[1] = (ret & 0xFFFF)%self.width + ret >>= 16 + self._position[0] = (ret & 0xFFFF)%self.height + ret >>= 16 + a = ret & 0xFF + ret >>= 8 + b = ret & 0xFF + ret >>= 8 + g = ret & 0xFF + ret >>= 8 + r = ret & 0xFF + + sa = a/255 + outa = (a + da*(1-sa))/255 + if outa == 0: + ro,go,bo = 0,0,0 + else: + ro, go, bo = [(c*(a/255)+dc*da*(1-sa))/outa + for c, dc in ((r,dr), (g, dg), (b, db))] + r,g,b,a = [int(e) for e in (ro,go,bo,outa*255)] + + + self._world[self._position[0]][self._position[1]] = [r,g,b,a] + + def score(self): + start = time.time() + + colcount = len(np.unique(np.reshape(self._world[:,:,:3], (-1,3)), + axis=0)) + + #gray = rgb2gray(self._world) + #sigma = estimate_sigma(self._world, multichannel=True, average_sigmas=True) + sigmas = estimate_sigma(self._world, multichannel=True, average_sigmas=False) + sigmas = [0 if np.isnan(sigma) else sigma for sigma in sigmas] + sigma = sum(sigmas)/len(sigmas) + scores = [fractal_dimension(self._world[:,:,i]*self._world[:,:,3]/255) + for i in range(3)] + scores += [fractal_dimension(self._world[:,:,3])] + mod = abs(scores[0]-scores[1]) + mod += abs(scores[0]-scores[2]) + mod += abs(scores[0]-scores[3]) + mod += abs(scores[1]-scores[2]) + mod += abs(scores[1]-scores[3]) + mod /= 5 + score = sum(scores)/len(scores) + + + score += mod + if sigma and sigma > 50: + score -= (sigma-50)/100 + colscore = abs(colcount-1024) / 1e5 + score -= colscore + + printscore = lambda arr: '['+(', '.join(['%1.3f' % e for e in arr]))+']' + print("colscore %3.3f (%4d colors) scores time %5.2fs" % (colscore, + colcount, + time.time() - start)) + print("SIGMAS : %s SIGMA : %f " % (printscore(sigmas), sigma)) + print("SCORES : %s SCORE : %r" % (printscore(scores), score)) + return score + diff --git a/python_rpnifs/ifs2.py b/python_rpnifs/ifs2.py new file mode 100644 index 0000000..727347b --- /dev/null +++ b/python_rpnifs/ifs2.py @@ -0,0 +1,89 @@ +import random +import copy + +import numpy as np +from skimage.restoration import estimate_sigma + +from .expr import * +from .fractdim import * + +alpha=32 +calpha = lambda n, c=255, a=10: -int(((c*a/255)-c)*(1-(1-a/255)**n)) +calphas = [[calpha(i, c, alpha) for i in range(0x1000)] + for c in range(256)] + +class IFS2(object): + """ @brief 1 Variable IFS with X,Y decomposition """ + + + #height=width=1024 + height=width=512 + + def __init__(self, nexpr=4, init_sz=1, world=None, alpha=16): + self._alpha = alpha + self._nexpr = nexpr + self._expr = [RpnExpr(sz=init_sz, nvar=1) + for _ in range(nexpr)] + self._world = world + if self._world is None: + self._world = self.get_world() + #self._position = [random.randint(0, self.height-1), + # random.randint(0, self.width-1)] + self._position = [self.height//2, self.width//2] + + @classmethod + def get_world(cls): + return np.zeros((cls.height,cls.width), dtype=np.uint32) + + def raz_world(self): + for i in range(self.height): + for j in range(self.width): + self._world[i][j] = 0 + + def __str__(self): + return ";".join([str(e) for e in self._expr]) + + def __copy__(self): + ret = IFS2(nexpr=self._nexpr, world=self._world) + ret._expr = [copy.copy(e) for e in self._expr] + #ret._world = copy.deepcopy(self._world) + return ret + + def mutation(self, n=1, rand=True): + n = 1 if n <= 1 else random.randint(1,n) + for _ in range(n): + random.choice(self._expr).mutation() + + def step(self): + arg = self._position[0] + arg <<=32 + arg += self._position[1] + + ret = int(random.choice(self._expr).eval(arg)) + + self._position[1] = (ret & 0xFFFFFFFF)%self.width + ret >>= 32 + self._position[0] = (ret & 0xFFFFFFFF)%self.height + + self._world[self._position[0]][self._position[1]] += 1 + + def get_image(self, color=(0,0xFF,0x0)): + return np.array([[[calpha(n, c, self._alpha) if n and c else 0 for c in color] + for n in line] + for line in self._world], dtype=np.uint8) + + + def score(self): + #maxi = self._world.max() + #mini = self._world.min() + #gray = np.array((self._world - mini)/(maxi-mini)*255, dtype=np.uint8) + gray = np.array([[calpha(n, 255, self._alpha) if n else 0 + for n in line] + for line in self._world], dtype=np.uint8) + sigma = estimate_sigma(gray, multichannel=False, average_sigmas=True) + sigma = 0 if np.isnan(sigma) else sigma + score = fractal_dimension(gray, 128) + + if sigma and sigma > 5: + score /= sigma/5 + return score diff --git a/python_rpnifs/ifs3.py b/python_rpnifs/ifs3.py new file mode 100644 index 0000000..ecd8852 --- /dev/null +++ b/python_rpnifs/ifs3.py @@ -0,0 +1,147 @@ +import random +import time +import copy + +import numpy as np +from skimage.restoration import estimate_sigma + +from .expr import * +from .fractdim import * + + +class IFS3(object): + """ @brief 1 Variable IFS with R,G,B,A X,Y decomposition """ + + #height=width=1024 + height=width=768 + #height=width=512 + + def __init__(self, nexpr=4, init_sz=1, world=None): + self._nexpr = nexpr + self._expr = [RpnExpr(sz=init_sz, nvar=1) + for _ in range(nexpr)] + self._world = world + if self._world is None: + self._world = self.get_world() + self._position = [random.randint(0, self.height-1), + random.randint(0, self.width-1)] + + @classmethod + def get_world(cls): + return np.zeros((cls.height,cls.width,4), dtype=np.uint8) + + def raz_world(self): + for i in range(self.height): + for j in range(self.width): + self._world[i][j] = [0,0,0,0] + + def __str__(self): + return ";".join([str(e) for e in self._expr]) + + def __copy__(self): + ret = IFS3(nexpr=self._nexpr, world=self._world) + ret._expr = [copy.copy(e) for e in self._expr] + #ret._world = copy.deepcopy(self._world) + return ret + + def get_image(self): + return self._world + + def mutation(self, n=1, rand=True): + n = 1 if n <= 1 else random.randint(1,n) + for _ in range(n): + random.choice(self._expr).mutation() + + def step(self): + r,g,b,a = [int(e) + for e in self._world[self._position[0]][self._position[1]]] + arg = r + arg <<= 8 + arg += g + arg <<= 8 + arg += b + arg <<= 8 + arg += a + arg <<=16 + arg += self._position[0] + arg <<=16 + arg += self._position[1] + + ret = int(random.choice(self._expr).eval(arg)) + dr, dg, db, da = r,g,b,a + + #self._position[1] = (ret & 0xFFFF)%self.width + self._position[1] = ((ret & 0xFFFF)*self.width)//0x10000 + ret >>= 16 + self._position[0] = ((ret & 0xFFFF)*self.height)//0x10000 + ret >>= 16 + a = ret & 0xFF + ret >>= 8 + b = ret & 0xFF + ret >>= 8 + g = ret & 0xFF + ret >>= 8 + r = ret & 0xFF + + sa = a/255 + outa = (a + da*(1-sa))/255 + if outa == 0: + ro,go,bo = 0,0,0 + else: + ro, go, bo = [(c*(a/255)+dc*da*(1-sa))/outa + for c, dc in ((r,dr), (g, dg), (b, db))] + r,g,b,a = [int(e) for e in (ro,go,bo,outa*255)] + + + self._world[self._position[0]][self._position[1]] = [r,g,b,a] + + def score(self): + start = time.time() + + colcount = len(np.unique(np.reshape(self._world[:,:,:3], (-1,3)), + axis=0)) + + #sigma = estimate_sigma(self._world, multichannel=True, average_sigmas=True) + sigmas = estimate_sigma(self._world[:,:,:3], multichannel=True, + average_sigmas=False) + + scores = [fractal_dimension(self._world[:,:,i]*self._world[:,:,3]/255) + for i in range(3)] + #alpha score + #scores += [fractal_dimension(self._world[:,:,3])] + + gray = rgb2gray(self._world) + graysigma = estimate_sigma(gray) + grayscore = fractal_dimension(gray) + del(gray) + + sigmas += [graysigma]*3 + sigmas = [0 if np.isnan(sigma) else sigma for sigma in sigmas] + + scores += [grayscore]*3 + + sigma = sum(sigmas)/len(sigmas) + + mod = abs(scores[0]-scores[1]) + mod += abs(scores[0]-scores[2]) + mod += abs(scores[0]-scores[3]) + mod += abs(scores[1]-scores[2]) + mod += abs(scores[1]-scores[3]) + mod /= 5 + score = sum(scores)/len(scores) + + + score += mod + if sigma and sigma > 0: + score -= sigma/100 + colscore = abs(colcount-1024) / 1e5 + score -= colscore + + printscore = lambda arr: '['+(', '.join(['%1.3f' % e for e in arr]))+']' + print("colscore %3.3f (%4d colors) scores time %5.2fs" % (colscore, + colcount, + time.time() - start)) + print("SIGMAS : %s SIGMA : %f " % (printscore(sigmas), sigma)) + print("SCORES : %s SCORE : %r" % (printscore(scores), score)) + return score + diff --git a/python_rpnifs/ifs4.py b/python_rpnifs/ifs4.py new file mode 100644 index 0000000..6ed64f7 --- /dev/null +++ b/python_rpnifs/ifs4.py @@ -0,0 +1,143 @@ +import random +import time +import copy + +import numpy as np +from skimage.restoration import estimate_sigma + +from .expr import * +from .fractdim import * + + +class IFS4(object): + """ @brief 1 Variable IFS with R,G,B,A X,Y decomposition """ + + #height=width=1024 + height=width=768 + #height=width=512 + + def __init__(self, nexpr=4, init_sz=1, world=None): + self._nexpr = nexpr + self._expr_pos = [[RpnExpr(sz=init_sz, nvar=6) + for _ in range(nexpr)] + for _ in range(2)] + self._expr_col = [[RpnExpr(sz=init_sz, nvar=6) + for _ in range(nexpr)] + for _ in range(4)] + self._world = world + if self._world is None: + self._world = self.get_world() + self._position = [random.randint(0, self.height-1), + random.randint(0, self.width-1)] + self._col = [random.randint(0, 255) for _ in range(4)] + + @classmethod + def get_world(cls): + return np.zeros((cls.height,cls.width,4), dtype=np.uint8) + + def raz_world(self): + for i in range(self.height): + for j in range(self.width): + self._world[i][j] = [0,0,0,0] + + def __str__(self): + ret = 'X<%s>' % (';'.join([str(e) for e in self._expr_pos[0]])) + ret += 'Y<%s>' % (';'.join([str(e) for e in self._expr_pos[1]])) + ret += 'R<%s>' % (';'.join([str(e) for e in self._expr_col[0]])) + ret += 'G<%s>' % (';'.join([str(e) for e in self._expr_col[1]])) + ret += 'B<%s>' % (';'.join([str(e) for e in self._expr_col[2]])) + ret += 'A<%s>' % (';'.join([str(e) for e in self._expr_col[3]])) + return ret + + def __copy__(self): + ret = IFS4(nexpr=self._nexpr, world=self._world) + ret._expr_pos = [[copy.copy(e) for e in el] + for el in self._expr_pos] + ret._expr_col = [[copy.copy(e) for e in el] + for el in self._expr_col] + #ret._world = copy.deepcopy(self._world) + return ret + + def get_image(self): + return self._world + + def mutation(self, n=1, rand=True): + n = 1 if n <= 1 else random.randint(1,n) + for _ in range(n): + random.choice(self._expr_pos + self._expr_col).mutation() + + def step(self): + dr,dg,db,da = [int(e) + for e in self._world[self._position[0]][self._position[1]]] + cx = int((self._position[0] / self.width) * ((1<<64)-1)) + cy = int((self._position[0] / self.height) * ((1<<64)-1)) + args = (cx,cy,dr,dg,db,da) + rx,ry = [int(random.choice(expr).eval(*args)) + for expr in self._expr_pos] + rx = (rx * self.width) // ((1<<64)-1) + ry = (ry * self.height) // ((1<<64)-1) + r,g,b,a = [int(random.choice(expr).eval(*args) % 256) + for expr in self._expr_col] + + sa = a/255 + outa = (a + da*(1-sa))/255 + if outa == 0: + ro,go,bo = 0,0,0 + else: + ro, go, bo = [(c*(a/255)+dc*da*(1-sa))/outa + for c, dc in ((r,dr), (g, dg), (b, db))] + r,g,b,a = [int(e) for e in (ro,go,bo,outa*255)] + + + self._world[self._position[0]][self._position[1]] = [r,g,b,a] + + def score(self): + start = time.time() + + colcount = len(np.unique(np.reshape(self._world[:,:,:3], (-1,3)), + axis=0)) + + #sigma = estimate_sigma(self._world, multichannel=True, average_sigmas=True) + sigmas = estimate_sigma(self._world[:,:,:3], multichannel=True, + average_sigmas=False) + + scores = [fractal_dimension(self._world[:,:,i]*self._world[:,:,3]/255) + for i in range(3)] + #alpha score + #scores += [fractal_dimension(self._world[:,:,3])] + + gray = rgb2gray(self._world) + graysigma = estimate_sigma(gray) + grayscore = fractal_dimension(gray) + del(gray) + + sigmas += [graysigma]*3 + sigmas = [0 if np.isnan(sigma) else sigma for sigma in sigmas] + + scores += [grayscore]*3 + + sigma = sum(sigmas)/len(sigmas) + + mod = abs(scores[0]-scores[1]) + mod += abs(scores[0]-scores[2]) + mod += abs(scores[0]-scores[3]) + mod += abs(scores[1]-scores[2]) + mod += abs(scores[1]-scores[3]) + mod /= 5 + score = sum(scores)/len(scores) + + + score += mod + if sigma and sigma > 0: + score -= sigma/100 + colscore = abs(colcount-1024) / 1e5 + score -= colscore + + printscore = lambda arr: '['+(', '.join(['%1.3f' % e for e in arr]))+']' + print("colscore %3.3f (%4d colors) scores time %5.2fs" % (colscore, + colcount, + time.time() - start)) + print("SIGMAS : %s SIGMA : %f " % (printscore(sigmas), sigma)) + print("SCORES : %s SCORE : %r" % (printscore(scores), score)) + return score + diff --git a/python_rpnifs/ifs5.py b/python_rpnifs/ifs5.py new file mode 100644 index 0000000..1f0fdfc --- /dev/null +++ b/python_rpnifs/ifs5.py @@ -0,0 +1,213 @@ +import random +import time +import copy + +import numpy as np +from skimage.restoration import estimate_sigma + +from .expr import * +from .fractdim import * +from .shape_score import ShapeScore + + +class IFS5(object): + """ @brief 1 Variable IFS with R,G,B,A X,Y decomposition """ + + #height=width=1024 + #height=width=768 + height=width=512 + + def __init__(self, nexpr=4, init_sz=1, world=None): + self._nexpr = nexpr + self._expr = [[RpnExpr(sz=init_sz, nvar=6) for _ in range(6)] + for _ in range(nexpr)] + self._world = world + if self._world is None: + self._world = self.get_world() + self._position = [random.randint(0, self.height-1), + random.randint(0, self.width-1)] + self._col = [random.randint(0, 255) for _ in range(4)] + + @classmethod + def get_world(cls): + return np.zeros((cls.height,cls.width,4), dtype=np.uint8) + + def raz_world(self): + for i in range(self.height): + for j in range(self.width): + self._world[i][j] = [0,0,0,0] + + def expr_dict(self): + el_lbl = 'XYrgba' + res = dict() + k_fmt = 'IFS:%02d:%c' + for i, expr in enumerate(self._expr): + for j, el in enumerate(expr): + res[k_fmt % (i, el_lbl[j])] = str(el) + return res + + def _fel(self): + el_lbl = 'XYrgba' + fel = lambda el: ';'.join(['%s=(%s)' % (el_lbl[i], str(el[i])) + for i in range(len(el))]) + return [fel(el) for el in self._expr] + + def __str__(self): + ret = ';'.join(['<%s>' % el for el in self._fel()]) + return ret + + def __copy__(self): + ret = IFS5(nexpr=self._nexpr, world=self._world) + ret._expr = [[copy.copy(e) for e in el] + for el in self._expr] + #ret._world = copy.deepcopy(self._world) + return ret + + def get_image(self): + return self._world + + def __sub__(self, b): + """ @return The sum of the levensthein distance between all expressions + of two ifs + """ + return sum([sum([self._expr[i][j] - b._expr[i][j] + for j in range(len(b._expr[i]))]) + for i in range(len(b._expr))]) + + def __add__(self, b): + """ @return A new IFS generated from both IFS, randomly choosing + expression from both + """ + if type(b) != type(self): + raise TypeError('Types are not IFS5 for add') + + ret = IFS5(nexpr=self._nexpr, world=self._world) + + rc = random.choice + rint = random.randint + + ret._expr = [[copy.copy(rc([self, b])._expr[rc([i, len(self._expr)-1])][rc([j, len(ex)-1])]) + for j, e in enumerate(ex)] + for i, ex in enumerate(self._expr)] + return ret + + def mutation(self, n=1, rand=True): + """ @brief Apply mutation on expressions + @param n the mutation count + @param rand unused ?! + """ + n = 1 if n <= 1 else random.randint(1,n) + for _ in range(n): + random.choice(random.choice(self._expr)).mutation() + + def step(self): + """ @brief Calculate a step of IFS + + Choose randomly an expression and evaluate it, giving values for + each argument and storing values in "world" + """ + dr,dg,db,da = [int(e) + for e in self._world[self._position[0]][self._position[1]]] + + cx = int((self._position[0] / self.width) * ((1<<64)-1)) + cy = int((self._position[0] / self.height) * ((1<<64)-1)) + + args = (cx,cy,dr,dg,db,da) + rx,ry,r,g,b,a = (int(expr.eval(*args)) + for expr in random.choice(self._expr)) + rx = (rx * (self.width-1)) // ((1<<64)-1) + ry = (ry * (self.height-1)) // ((1<<64)-1) + self._position[0], self._position[1] = rx, ry + r,g,b,a = (c%256 for c in (r,g,b,a)) + + sa = a/255 + outa = (a + da*(1-sa))/255 + if outa == 0: + ro,go,bo = 0,0,0 + else: + ro, go, bo = [(c*(a/255)+dc*da*(1-sa))/outa + for c, dc in ((r,dr), (g, dg), (b, db))] + r,g,b,a = [int(e) for e in (ro,go,bo,outa*255)] + + + self._world[self._position[0]][self._position[1]] = [r,g,b,a] + + def score(self): + """ @brief Calculate the world's score + @return a float, higher the better + """ + start = time.time() + + colcount = len(np.unique(np.reshape(self._world[:,:,:3], (-1,3)), + axis=0)) + + #sigma = estimate_sigma(self._world, multichannel=True, average_sigmas=True) + sigmas = estimate_sigma(self._world[:,:,:3], multichannel=True, + average_sigmas=False) + + scores = [fractal_dimension(self._world[:,:,i]*self._world[:,:,3]/255) + for i in range(3)] + #alpha score + #scores += [fractal_dimension(self._world[:,:,3])] + + gray = rgba2gray(self._world) + graysigma = estimate_sigma(gray) + grayscore = fractal_dimension(gray) + del(gray) + + sigmas += [graysigma]*3 + sigmas = [0 if np.isnan(sigma) else sigma for sigma in sigmas] + + scores += [grayscore]*3 + + sigma_zero = 0 + for sig in sigmas: + if sig == 0: + sigma_zero += 1 + if len(sigmas) > sigma_zero: + sigma = sum(sigmas)/(len(sigmas)-sigma_zero) + else: + sigma = 0 + + mod = abs(scores[0]-scores[1]) + mod += abs(scores[0]-scores[2]) + mod += abs(scores[0]-scores[3]) + mod += abs(scores[1]-scores[2]) + mod += abs(scores[1]-scores[3]) + mod /= 5 + + null_comp = 0 + for i in range(3): + null_comp += 1 if scores[i] <= 0 else 0 + + if null_comp >= 2: + score = 0 + mod *= 0.8 + else: + zero_score=0 + for score in scores: + if score == 0: + zero_score += 1 + if len(scores) < zero_score: + score = sum(scores)/(len(scores)-zero_score) + else: + score = 0 + + score += mod + + if sigma and sigma > 0: + score -= sigma/100 + #colscore = abs(colcount-1024) / 1e5 + colscore = abs(colcount-2048) / 1e5 + if colcount < 2048: + colscore *= 10 + score -= colscore + + printscore = lambda arr: '['+(', '.join(['%1.3f' % e for e in arr]))+']' + print("colscore %3.3f (%4d colors) scores time %5.2fs" % (colscore, + colcount, + time.time() - start)) + print("SIGMAS : %s SIGMA : %f " % (printscore(sigmas), sigma)) + print("SCORES : %s SCORE : %r" % (printscore(scores), score)) + return score + diff --git a/python_rpntoken.c b/python_rpntoken.c new file mode 100644 index 0000000..2bc7530 --- /dev/null +++ b/python_rpntoken.c @@ -0,0 +1,504 @@ +#include "python_rpntoken.h" +/**@file python_rpntoken.c + * @brief Python module & type definition + * @ingroup python_ext + * @ingroup python_pyrpn_token + * @ingroup python_pyrpn_token_Token + * @ingroup python_pyrpn_token_TokenOp + * @ingroup python_pyrpn_token_TokenVal + * @ingroup python_pyrpn_token_TokenArg + */ + +/**@brief @ref pymod_pyrpn_token_Token method definition + * @ingroup python_pyrpn_token_Token */ +static PyMethodDef RPNToken_methods[] = { + PYRPN_method("from_str", rpntoken_from_str, METH_CLASS | METH_O, + "cls, token_str, /", + "Return a new RPNToken subclass instance from string"), + {NULL} // +}; + +PyTypeObject RPNTokenType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "pyrpn.tokens.Token", + .tp_doc = "Abstract class for RPN expression tokens", + .tp_basicsize = sizeof(RPNToken_t), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_richcompare = rpntoken_richcompare, + .tp_init = rpntoken_init, + .tp_new = PyType_GenericNew, + .tp_str = rpntoken_str, + .tp_repr = rpntoken_repr, + .tp_methods = RPNToken_methods, +}; + +/**@brief @ref pymod_pyrpn_token_TokenOp method definition + * @ingroup python_pyrpn_token_TokenOp */ +static PyMethodDef RPNTokenOp_methods[] = { + PYRPN_method("opcode_max", rpntokenop_opcode_max, + METH_STATIC | METH_NOARGS, + "", + "Return the maximum valid value for an opcode"), + PYRPN_method("chr", rpntokenop_opchr, METH_NOARGS, + "self, /", + "Return the single char representation of operand"), + PYRPN_method("str", rpntokenop_opstr, METH_NOARGS, + "self, /", + "Return the string (multi-char) representation of the operand"), + {NULL} // +}; + +/**@brief @ref pymod_pyrpn_token_TokenOp members definition + * @ingroup python_pyrpn_token_TokenOp */ +static PyMemberDef RPNTokenOp_members[] = { + {"opcode", T_BYTE, offsetof(RPNTokenOp_t, super.value.op_n), READONLY, + "The number representing the operand"}, + /* + {"chr", T_CHAR, offsetof(RPNTokenOp_t, super.value.op->chr), READONLY, + "The single char representation of the operand"}, + {"str", T_STRING, offsetof(RPNTokenOp_t, super.value.op->str), READONLY, + "The str representation of the operand"}, + */ + {NULL} // +}; + +PyTypeObject RPNTokenOpType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_base = &RPNTokenType, + .tp_name = "pyrpn.tokens.Operand", + .tp_doc = "RPN expression operand token", + .tp_basicsize = sizeof(RPNTokenOp_t), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_init = rpntokenop_init, + .tp_members = RPNTokenOp_members, + .tp_methods = RPNTokenOp_methods, +}; + +/**@brief @ref pymod_pyrpn_token_TokenArg members definition + * @ingroup python_pyrpn_token_TokenArg */ +static PyMemberDef RPNTokenArg_members[] = { + {"argno", T_ULONG, offsetof(RPNTokenOp_t, super.value.arg_n), READONLY, + "The argument number"}, + {NULL} // +}; + +PyTypeObject RPNTokenArgType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_base = &RPNTokenType, + .tp_name = "pyrpn.tokens.Argument", + .tp_doc = "RPN expression argument token", + .tp_basicsize = sizeof(RPNTokenArg_t), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_init = rpntokenarg_init, + .tp_members = RPNTokenArg_members, +}; + +/**@brief @ref pymod_pyrpn_token_TokenVal members definition + * @ingroup python_pyrpn_token_TokenVal */ +static PyMemberDef RPNTokenVal_members[] = { + {"value", T_ULONG, offsetof(RPNTokenOp_t, super.value.value), READONLY, + "The immediate value"}, + {NULL} // +}; + +PyTypeObject RPNTokenValType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_base = &RPNTokenType, + .tp_name = "pyrpn.tokens.Value", + .tp_doc = "RPN expression value token", + .tp_basicsize = sizeof(RPNTokenVal_t), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_init = rpntokenval_init, + .tp_members = RPNTokenVal_members, +}; + +PyModuleDef rpntokens_module = { + PyModuleDef_HEAD_INIT, + .m_name = "pyrpn.tokens", + .m_doc = "RPN expression tokens classes", + .m_size = -1, + .m_methods = NULL, + .m_clear = NULL, +}; + + +PyObject* rpntokens_module_init(void) +{ + PyObject *mod; + + mod = PyModule_Create(&rpntokens_module); + if(!mod) { return NULL; } + + if(PyType_Ready(&RPNTokenType) < 0 + || PyType_Ready(&RPNTokenOpType) < 0 \ + || PyType_Ready(&RPNTokenValType) < 0 \ + || PyType_Ready(&RPNTokenArgType) < 0) + { + return NULL; + } + Py_INCREF(&RPNTokenType); + Py_INCREF(&RPNTokenOpType); + Py_INCREF(&RPNTokenValType); + Py_INCREF(&RPNTokenArgType); + + if( PyModule_AddObject(mod, "Token", + (PyObject*)&RPNTokenType) < 0 \ + || PyModule_AddObject(mod, "Operand", + (PyObject*)&RPNTokenOpType) < 0 \ + || PyModule_AddObject(mod, "Value", + (PyObject*)&RPNTokenValType) < 0 \ + || PyModule_AddObject(mod, "Argument", + (PyObject*)&RPNTokenArgType) < 0) + { + Py_DECREF(&RPNTokenType); + Py_DECREF(&RPNTokenOpType); + Py_DECREF(&RPNTokenValType); + Py_DECREF(&RPNTokenArgType); + return NULL; + } + + return mod; +} + +PyObject* rpntoken_from_str(PyObject *cls, PyObject *arg) +{ + if(!PyUnicode_Check(arg)) + { + PyErr_SetString(PyExc_TypeError, "Expected argument to be a str"); + return NULL; + } + PyObject *bytes_str = PyUnicode_AsASCIIString(arg); + if(PyErr_Occurred()) + { + return NULL; + } + const char *str = PyBytes_AS_STRING(bytes_str); + rpn_token_t token; + + char err_str[64]; + if(rpn_tokenize(str, &token, err_str) < 0) + { + Py_DECREF(bytes_str); + PyObject *ascii = PyObject_ASCII(arg); + PyErr_Format(PyExc_ValueError, "Unable to parse %s", ascii); + Py_DECREF(ascii); + return NULL; + } + Py_DECREF(bytes_str); + + return rpntoken_from_token(&token); +} + + +PyObject* rpntoken_from_token(const rpn_token_t *token) +{ + PyTypeObject *type; + + switch(token->type) + { + case RPN_op: + type = &RPNTokenOpType; + break; + case RPN_val: + type = &RPNTokenValType; + break; + case RPN_arg: + type = &RPNTokenArgType; + break; + default: + PyErr_SetString(PyExc_RuntimeError, + "Unrecognized parsed token"); + return NULL; + } + + PyObject *ret = PyObject_CallMethod((PyObject*)type, "__new__", "O", type); + if(PyErr_Occurred()) + { + return NULL; + } + // Any child type should work here since struct begins with super + RPNToken_t *_ret = (RPNToken_t*)ret; + memcpy(&_ret->value, token, sizeof(*token)); + return ret; + +} + + +int rpntoken_init(PyObject *_self, PyObject *args, PyObject *kwds) +{ + PyErr_SetString(PyExc_NotImplementedError, "Abstract class"); + return -1; +} + +PyObject* rpntoken_richcompare(PyObject *_self, PyObject *_other, int op) +{ + RPNToken_t *self, *other; + self = (RPNToken_t*)_self; + other = (RPNToken_t*)_other; + + + if(!PyObject_IsInstance(_other, (PyObject*)&RPNTokenType)) + { + PyErr_Format(PyExc_TypeError, + "Can only be compared with RPNToken subtypes"); + return NULL; + } + + int cmp = self->value.type - other->value.type; + if(cmp == 0) + { + switch(self->value.type) + { + case RPN_op: + cmp = self->value.op_n - other->value.op_n; + break; + case RPN_arg: + cmp = self->value.arg_n - other->value.arg_n; + break; + case RPN_val: + cmp = self->value.value - other->value.value; + break; + default: + PyErr_Format(PyExc_RuntimeError, + "Unknown token type %d", + self->value.type); + return NULL; + } + } + + switch(op) + { + case Py_LT: + if(cmp < 0) { Py_RETURN_TRUE; } + Py_RETURN_FALSE; + case Py_LE: + if(cmp <= 0) { Py_RETURN_TRUE; } + Py_RETURN_FALSE; + case Py_EQ: + if(cmp == 0) { Py_RETURN_TRUE; } + Py_RETURN_FALSE; + case Py_NE: + if(cmp != 0) { Py_RETURN_TRUE; } + Py_RETURN_FALSE; + case Py_GT: + if(cmp > 0) { Py_RETURN_TRUE; } + Py_RETURN_FALSE; + case Py_GE: + if(cmp >= 0) { Py_RETURN_TRUE; } + Py_RETURN_FALSE; + default: + PyErr_Format(PyExc_NotImplementedError, + "Unknown comparison %d", + self->value.type); + return NULL; + } +} + +PyObject* rpntoken_repr(PyObject *_self) +{ + RPNToken_t *self = (RPNToken_t*)_self; + + + int needed_sz = rpn_token_snprintf(&self->value, NULL, 0); + if(needed_sz < 0) + { + PyErr_Format(PyExc_RuntimeError, "Error : %s", strerror(errno)); + return NULL; + } + char str[needed_sz+1]; + rpn_token_snprintf(&self->value, str, needed_sz+1); + + PyTypeObject *tp = Py_TYPE(_self); + PyObject *tp_name = PyType_GetName(tp); + PyObject *bytes_str = PyUnicode_AsASCIIString(tp_name); + Py_DECREF(tp_name); + const char *typename = PyBytes_AS_STRING(bytes_str); + + needed_sz = snprintf(NULL, 0, "<%s '%s'>", typename, str); + char res_str[needed_sz+1]; + needed_sz = snprintf(res_str, needed_sz+1, "<%s '%s'>", typename, str); + Py_DECREF(bytes_str); + + return PyUnicode_FromString(res_str); +} + +PyObject* rpntoken_str(PyObject *_self) +{ + RPNToken_t *self = (RPNToken_t*)_self; + + int needed_sz = rpn_token_snprintf(&self->value, NULL, 0); + if(needed_sz < 0) + { + PyErr_Format(PyExc_RuntimeError, "Error : %s", strerror(errno)); + return NULL; + } + char str[needed_sz+1]; + rpn_token_snprintf(&self->value, str, needed_sz+1); + return PyUnicode_FromString(str); +} + + +int rpntokenop_init(PyObject *_self, PyObject *args, PyObject *kwds) +{ + RPNTokenOp_t *self = (RPNTokenOp_t*)_self; + PyObject *pyop = NULL; + char *names[] = {"op", NULL}; + + + if(!PyArg_ParseTupleAndKeywords(args, kwds, "O:RPNTokenOP.__init__", + names, + &pyop)) + { + return -1; + } + + Py_INCREF(pyop); + + if(PyLong_Check(pyop)) + { + //opcode given ? + long opcode = PyLong_AsLong(pyop); + if(PyErr_Occurred()) { return -1; } + if(opcode < 0) + { + PyErr_SetString(PyExc_ValueError, "Opcode cannot be negative"); + return -1; + } + else if ((size_t)opcode >= rpn_op_sz()) + { + PyErr_Format(PyExc_ValueError, + "Maximum opcode is %ld but %ld given", + rpn_op_sz()-1, opcode); + return -1; + } + self->super.value.op_n = opcode; + self->super.value.op = rpn_op_from_opcode(opcode); + } + else if(PyUnicode_Check(pyop)) + { + PyObject *bytes_str = PyUnicode_AsASCIIString(pyop); + if(PyErr_Occurred()) + { + return -1; + } + const char *token_str = PyBytes_AS_STRING(bytes_str); + char err_str[64]; + + if(rpn_tokenize(token_str, &(self->super.value), err_str) < 0) + { + Py_DECREF(bytes_str); + PyObject *ascii = PyObject_ASCII(pyop); + PyErr_Format(PyExc_ValueError, "Unrecognized token '%s' : %s", + ascii, err_str); + Py_DECREF(ascii); + goto err; + } + Py_DECREF(bytes_str); + if(self->super.value.type != RPN_op) + { + PyErr_SetString(PyExc_TypeError, "Decoded token is not an operand"); + goto err; + } + } + else + { + PyErr_SetString(PyExc_TypeError, "Given argument is neither str neither int"); + goto err; + + } + + Py_DECREF(pyop); + return 0; + +err: + if(pyop) + { + Py_DECREF(pyop); + } + return -1; +} + +PyObject *rpntokenop_opcode_max(PyObject* Py_UNUSED(_static)) +{ + return PyLong_FromSize_t(rpn_op_sz()-1); +} + +PyObject *rpntokenop_opchr(PyObject *_self, PyObject* Py_UNUSED(_null)) +{ + RPNTokenOp_t *self = (RPNTokenOp_t*)_self; + if(self->super.value.op->chr == '\0') + { + Py_RETURN_NONE; + } + char buf[2]; + buf[1] = '\0'; + buf[0] = self->super.value.op->chr; + return PyUnicode_FromString(buf); +} + + +PyObject *rpntokenop_opstr(PyObject *_self, PyObject* Py_UNUSED(_null)) +{ + RPNTokenOp_t *self = (RPNTokenOp_t*)_self; + if(!self->super.value.op->str) + { + Py_RETURN_NONE; + } + Py_RETURN_NONE; + return PyUnicode_FromString(self->super.value.op->str); +} + +int rpntokenval_init(PyObject *_self, PyObject *args, PyObject *kwds) +{ + RPNTokenOp_t *self = (RPNTokenOp_t*)_self; + char *names[] = {"value", NULL}; + PyObject *arg = NULL; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, "O:RPNTokenVal.__init__", + names, &arg)) + { + return -1; + } + if(!PyLong_Check(arg)) + { + PyErr_SetString(PyExc_TypeError, "Expected integer as argument"); + return -1; + } + self->super.value.value = PyLong_AsUnsignedLong(arg); + if(PyErr_Occurred()) + { + return -1; + } + self->super.value.type = RPN_val; + return 0; +} + +int rpntokenarg_init(PyObject *_self, PyObject *args, PyObject *kwds) +{ + RPNTokenOp_t *self = (RPNTokenOp_t*)_self; + char *names[] = {"argno", NULL}; + PyObject *arg = NULL; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, "O:RPNTokenArg.__init__", + names, &arg)) + { + return -1; + } + if(!PyLong_Check(arg)) + { + PyErr_SetString(PyExc_TypeError, "Expected integer as argument"); + return -1; + } + self->super.value.arg_n = PyLong_AsUnsignedLong(arg); + if(PyErr_Occurred()) + { + return -1; + } + self->super.value.type = RPN_arg; + return 0; +} + diff --git a/python_rpntoken.h b/python_rpntoken.h new file mode 100644 index 0000000..99f25fb --- /dev/null +++ b/python_rpntoken.h @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2023 Weber Yann + * + * This file is part of pyrpn. + * + * pyrpn is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * pyrpn is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with pyrpn. If not, see . + */ +#ifndef _PYTHON_RPNTOKEN_H__ +#define _PYTHON_RPNTOKEN_H__ + +#include "config.h" +#include "rpn_parse.h" + +#define PY_SSIZE_T_CLEAN +#include +#include "structmember.h" + +/**@file python_rpntoken.h + * @brief Python RPNToken type headers + * @ingroup python_ext + * @ingroup pymod_pyrpn_token + * + * This file is the header of the RPNToken classes and subclasses + * + */ + +/**@defgroup pymod_pyrpn_token module pyrpn.token + * @brief Python module representing RPNExpr tokens + * @ingroup pymod_pyrpn + */ +/**@brief pyrpn.token module */ +extern PyModuleDef rpntokens_module; + +/**@defgroup pymod_pyrpn_token_Token pyrpn.token.Token + * @brief Abstract class representing an @ref pymod_pyrpn_RPNExpr token + * @ingroup pymod_pyrpn_token */ +/**@brief pyrpn.token.Token generic type + * @ingroup pymod_pyrpn_token_Token */ +extern PyTypeObject RPNTokenType; + +/**@defgroup pymod_pyrpn_token_TokenOp pyrpn.token.TokenOp + * @brief Python class representing an @ref pymod_pyrpn_RPNExpr operation token + * + * Extends @ref pymod_pyrpn_token_Token + * @ingroup pymod_pyrpn_token */ +/**@brief pyrpn.token.TokenOp type + * @ingroup pymod_pyrpn_token_TokenOp */ +extern PyTypeObject RPNTokenOpType; + +/**@defgroup pymod_pyrpn_token_TokenVal pyrpn.token.TokenVal + * @brief Python class representing an @ref pymod_pyrpn_RPNExpr value token + * + * Extends @ref pymod_pyrpn_token_Token + * @ingroup pymod_pyrpn_token */ +/**@brief pyrpn.token.TokenVal type + * @ingroup pymod_pyrpn_token_TokenVal */ +extern PyTypeObject RPNTokenValType; + +/**@defgroup pymod_pyrpn_token_TokenArg pyrpn.token.TokenArg + * @brief Python class representing an @ref pymod_pyrpn_RPNExpr argument token + * + * Extends @ref pymod_pyrpn_token_Token + * @ingroup pymod_pyrpn_token */ +/**@brief pyrpn.token.TokenArg type + * @ingroup pymod_pyrpn_token_TokenArg */ +extern PyTypeObject RPNTokenArgType; + + +/**@brief C representation of @ref RPNTokenType generic class + * @ingroup pymod_pyrpn_token_Token */ +typedef struct +{ + /** Python's type definition */ + PyObject_HEAD; + /** @brief The token value (will be set by subtypes) */ + rpn_token_t value; +} RPNToken_t; + +/**@brief C representation of all @ref RPNTokenType subclasses + * @ingroup pymod_pyrpn_token_tokenOp + * @ingroup pymod_pyrpn_token_tokenVal + * @ingroup pymod_pyrpn_token_tokenArg + */ +typedef struct { + /** @brief Pointer on super type */ + RPNToken_t super; +} RPNTokenSubClass_t; + +/**@brief C representation of @ref RPNTokenOpType + * @ingroup pymod_pyrpn_token_TokenOp */ +typedef RPNTokenSubClass_t RPNTokenOp_t; +/**@brief C representation of @ref RPNTokenValType + * @ingroup pymod_pyrpn_token_TokenVal */ +typedef RPNTokenSubClass_t RPNTokenVal_t; +/**@brief C representation of @ref RPNTokenArgType + * @ingroup pymod_pyrpn_token_TokenArg */ +typedef RPNTokenSubClass_t RPNTokenArg_t; + + +/**@brief Module initialisation function + * @return initialized module + * @ingroup pymod_pyrpn_token */ +PyObject* rpntokens_module_init(void); + +/**@brief Class method returning a token instance from a string representation + * @param cls @ref RPNTokenType class or any subclass + * @param arg A str representing a token + * @return A new @ref RPNTokenType subclass instance or NULL on error + * @ingroup pymod_pyrpn_token_Token + */ +PyObject* rpntoken_from_str(PyObject *cls, PyObject *arg); + +/**@brief Instanciate a new RPNToken subclass given a C token + * @param token An expression token + * @return A new @ref RPNTokenType subclass instance + * @ingroup pymod_pyrpn_token_Token + */ +PyObject* rpntoken_from_token(const rpn_token_t *token); + + +/**@brief @ref RPNTokenType __init__ method + * @param _self @ref RPNTokenType subclass instance + * @param args Positional arguments + * @param kwds Keyword arguments + * @return 0 or -1 on error + * @ingroup pymod_pyrpn_token_Token + */ +int rpntoken_init(PyObject *_self, PyObject *args, PyObject *kwds); + +/**@brief PEP-207 comparison method + * @param _self An @ref RPNTokenType subclass instance + * @param other The @ref RPNTokenType subclass instance to compare to + * @param op The comparison + * @return Py_True or Py_False + * @ingroup pymod_pyrpn_token_Token + */ +PyObject* rpntoken_richcompare(PyObject *_self, PyObject *other, int op); + +/**@brief __str__ method + * @param _self An @ref RPNTokenType subclass instance + * @return A str representing the token + * @ingroup pymod_pyrpn_token_Token */ +PyObject* rpntoken_str(PyObject *_self); + +/**@brief __repr__ method + * @param _self An @ref RPNTokenType subclass instance + * @return A str representing the token + * @ingroup pymod_pyrpn_token_Token */ +PyObject* rpntoken_repr(PyObject *_self); + + + + + +/**@brief @ref RPNTokenOpType __init__ method + * @param _self RPNTokenOpType being initialized + * @param args Positional arguments + * @param kwds Keyword arguments + * @return 0 or -1 on error + * @ingroup pymod_pyrpn_token_TokenOp */ +int rpntokenop_init(PyObject *_self, PyObject *args, PyObject *kwds); + +/**@brief @ref RPNTokenOpType opcode_max method + * @param Py_UNUSED unused argument for static method + * @return The maximum valid opcode + * @ingroup pymod_pyrpn_token_TokenOp */ +PyObject *rpntokenop_opcode_max(PyObject *Py_UNUSED(_static)); + +/**@brief @ref RPNTokenOpType chr method + * @param _self @ref RPNTokenOpType instance + * @param Py_UNUSED unused argument + * @return A string with a single chr representing the operation + * @ingroup pymod_pyrpn_token_TokenOp */ +PyObject *rpntokenop_opchr(PyObject *_self, PyObject* Py_UNUSED(_null)); + +/**@brief @ref RPNTokenOpType str method + * @param _self @ref RPNTokenOpType instance + * @param Py_UNUSED unused argument + * @return A string representing the operation on multiple chr + * @ingroup pymod_pyrpn_token_TokenOp */ +PyObject *rpntokenop_opstr(PyObject *_self, PyObject* Py_UNUSED(_null)); + + +/**@brief @ref RPNTokenValType __init__ method + * @param _self RPNTokenValType being initialized + * @param args Positional arguments + * @param kwds Keyword arguments + * @return 0 or -1 on error + * @ingroup pymod_pyrpn_token_TokenVal */ +int rpntokenval_init(PyObject *_self, PyObject *args, PyObject *kwds); + +/**@brief @ref RPNTokenArgType __init__ method + * @param _self RPNTokenArgType being initialized + * @param args Positional arguments + * @param kwds Keyword arguments + * @return 0 or -1 on error + * @ingroup pymod_pyrpn_token_TokenArg */ +int rpntokenarg_init(PyObject *_self, PyObject *args, PyObject *kwds); + +#endif diff --git a/rpn_if.c b/rpn_if.c index d91a26e..4711da5 100644 --- a/rpn_if.c +++ b/rpn_if.c @@ -18,7 +18,8 @@ */ #include "rpn_if.h" -rpn_if_t* rpn_if_new(const rpn_if_param_t *params, rpn_value_t *memmap) +rpn_if_t* rpn_if_new(const rpn_if_param_t *params, rpn_value_t *memmap, + rpn_expr_t *init_exprs) { rpn_if_t *res; size_t i; @@ -27,6 +28,11 @@ rpn_if_t* rpn_if_new(const rpn_if_param_t *params, rpn_value_t *memmap) res = malloc(sizeof(rpn_if_t)); if(!res) { + #if DEBUG + err = errno; + perror("rpn_if_new() struct malloc failed"); + errno = err; + #endif goto error; } @@ -41,10 +47,16 @@ rpn_if_t* rpn_if_new(const rpn_if_param_t *params, rpn_value_t *memmap) else { res->self_mem = 1; - res->mem = mmap(NULL, params->mem_sz, PROT_READ|PROT_WRITE, MAP_ANON, - -1, 0); + res->mem = mmap(NULL, params->mem_sz * params->value_sz, + PROT_READ|PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0); if(res->mem == (void*)-1) { + #if DEBUG + err = errno; + perror("rpn_if_new() mmap failed"); + errno = err; + #endif goto mmap_err; } } @@ -53,13 +65,54 @@ rpn_if_t* rpn_if_new(const rpn_if_param_t *params, rpn_value_t *memmap) (params->rpn_sz + params->rpn_argc)); if(!res->rpn_res) { + #if DEBUG + err = errno; + perror("rpn_if_new() rpn_res malloc failed"); + errno = err; + #endif goto rpn_malloc_err; } + res->rpn_args = &(res->rpn_res[params->rpn_sz]); - res->rpn = malloc(sizeof(rpn_expr_t*) * params->rpn_sz); + if(init_exprs) + { // using existing exppressions, checking them + short err = 0; + for(size_t i=0; irpn_sz; i++) + { + if(init_exprs[i].args_count != params->rpn_argc) + { + err = 1; + snprintf(init_exprs[i].err_reason, 128, + "Expected %ld arguments but expression got %ld", + params->rpn_argc, + init_exprs[i].args_count); + } + else if(init_exprs[i].stack_sz != params->rpn_stack_sz) + { + err = 1; + snprintf(init_exprs[i].err_reason, 128, + "Expected %d element stack but expression got %d", + params->rpn_stack_sz, + init_exprs[i].stack_sz); + } + } + if(err) + { + goto rpn_malloc_err; + } + res->rpn = init_exprs; + return res; + } + + res->rpn = malloc(sizeof(rpn_expr_t) * params->rpn_sz); if(!res->rpn) { + #if DEBUG + err = errno; + perror("rpn_if_new() rpn expr malloc failed"); + errno = err; + #endif goto rpn_expr_err; } for(i=0; irpn_sz; i++) @@ -67,8 +120,14 @@ rpn_if_t* rpn_if_new(const rpn_if_param_t *params, rpn_value_t *memmap) if(rpn_expr_init(&(res->rpn[i]), params->rpn_stack_sz, params->rpn_argc) < 0) { + #if DEBUG + err = errno; + perror("rpn_if_new() rpn_expr_init() failed"); + errno = err; + #endif goto rpn_init_error; } + rpn_expr_compile(&(res->rpn[i]), ""); } return res; @@ -87,7 +146,7 @@ rpn_if_t* rpn_if_new(const rpn_if_param_t *params, rpn_value_t *memmap) err = errno; if(res->self_mem) { - munmap(res->mem, params->mem_sz); + munmap(res->mem, params->mem_sz * params->value_sz); } mmap_err: err = errno; @@ -109,26 +168,26 @@ void rpn_if_free(rpn_if_t* rif) free(rif->rpn_res); if(rif->self_mem) { - munmap(rif->mem, rif->params->mem_sz); + munmap(rif->mem, rif->params->mem_sz * rif->params->value_sz); } free(rif); } size_t rpn_if_step(rpn_if_t *rif, size_t pos) { - size_t i; size_t newpos; - rif->params->arg_f(rif, pos, rif->rpn_args); - /* WRONG ! rif->rpn_args is in rif structure and do not have to be - given as argument... */ - for(i=0; iparams->rpn_sz; i++) + + assert(rif != NULL); + assert(rif->params != NULL); + assert(rif->params->getarg_f != NULL); + + rif->params->getarg_f(rif, pos); + for(size_t i=0; iparams->rpn_sz; i++) { - rif->rpn_res[i] = rpn_expr_eval(&(rif->rpn[i]), rif->rpn_args); + rpn_value_t res = rpn_expr_eval(&(rif->rpn[i]), rif->rpn_args); + rif->rpn_res[i] = res; } - //rif->params->res_f(rif, &newpos, rif->rpn_res); - /* MEGA WRONG ! rif->rpn_res is in rif structure and do not have to be - given as argument... */ - rif->params->res_f(rif, &newpos, NULL); + rif->params->setres_f(rif, &newpos); return newpos; } diff --git a/rpn_if.h b/rpn_if.h index 6093525..4294a04 100644 --- a/rpn_if.h +++ b/rpn_if.h @@ -21,6 +21,8 @@ #include "config.h" +#include + #include "rpn_jit.h" /**@file rpn_if.h @@ -34,12 +36,29 @@ * @ingroup ifs * @brief Iterated RPN expression * - * A single Iterated Function implemented using an RPN expression. + * @note For more details about Iterated function see dedicated + * section : @ref doc_ifs_if * - * @note The goal is to optimize iteration writing the code in C and providing - * an high level Python API. + * Iterated function are function that can be iterated on successive position + * in a memory map. The function takes its arguments from a position in the + * memory map and output its results at a resulting position in the memory map. * - * For more details about IF see dedicated page : @ref doc_ifs_if + * The iterated function can be seen as a function taking a position as argument + * and outputing another position with borders effects from data stored at given + * positions. + * + * The implementation try to be as generic as possible, allowing to implement + * the transformation using a @ref rpn_expr_t system. The iterated function + * has 2 functions as parameters : + * - The first one @ref rpn_if_param_s.getarg_f transform a position + * to argument(s) and fetch the other arguments from the memory map + * - The second @ref rpn_if_param_s.setres_f takes the results from the + * @ref rpn_expr_t system, transform it in position and results and set + * the memory map at resulting position with results. + * + * Based on this generic implementation @ref ifs_if_default are implemented for + * various common cases : mutli-dimentionnal positions and constants, boolean, + * colors as results. */ /**@brief Shortcut for struct @ref rpn_if_s */ @@ -52,7 +71,8 @@ struct rpn_if_param_s { /**@brief Memory map size in items */ size_t mem_sz; - /**@brief Size of a memory item */ + /**@brief Size of a memory item in bytes (should be a multiple of + sizeof(rpn_value_t))*/ size_t value_sz; /**@brief RPN expression count */ size_t rpn_sz; @@ -65,12 +85,12 @@ struct rpn_if_param_s /**@brief Set RPN arguments given a position * @note transform position to arguments and fetch other arguments * from memory map*/ - int (*arg_f)(rpn_if_t *rif, size_t pos, rpn_value_t *args); + int (*getarg_f)(rpn_if_t *rif, size_t pos); /**@brief RPN results to data and position transformation * @note set memory maps with converted data */ - int (*res_f)(rpn_if_t *rif, size_t *pos, - rpn_value_t *data); + int (*setres_f)(rpn_if_t *rif, size_t *pos); + /**@brief Arbitrary data, if set must be freed in one free() call */ void *data; }; @@ -95,15 +115,17 @@ struct rpn_if_s /**@brief Macro fetching a memory pointer given a position * @return rpn_value_t* values */ -#define rpn_if_getitem(rif, pos) (rif->mem + ((rif->params->value_sz) * pos)) +#define rpn_if_getitem(rif, pos) (rpn_value_t*)(((unsigned char*)rif->mem + ((rif->params->value_sz) * pos))) /**@brief Alloc a new @ref rpn_if_s using given parameters * @param params IF parameters - * @param rpn list of RPN expresions of params->rpn_sz size * @param memmap A suitable memory map. If NULL given, a new mmap is used + * @param init_exprs If no NULL uses this expression (steal refs), else uses + * new empty expressions * @return A pointer on an allocated @ref rpn_if_s */ -rpn_if_t* rpn_if_new(const rpn_if_param_t *params, rpn_value_t *memmap); +rpn_if_t* rpn_if_new(const rpn_if_param_t *params, rpn_value_t *memmap, + rpn_expr_t *init_exprs); /**@brief Deallocate an @ref rpn_if_s and its ressources and close associated * @ref rpn_expr_s @@ -120,11 +142,13 @@ size_t rpn_if_step(rpn_if_t *rif, size_t pos); /**@brief Returns the list of RPN expression : allowing to recompile them + * @param rif Pointer on IF * @return A list of RPN expressions * @note The memory area returned must not be freed ! */ rpn_expr_t **rpn_if_rpn_get(rpn_if_t *rif); + /**@brief New @ref rpn_if_s and partial initialisation * @param mem_sz memory size in bytes * @param rpn_argc number of arguments taken by @ref rpn_expr_s diff --git a/rpn_if_default.c b/rpn_if_default.c index fd09d50..dae670c 100644 --- a/rpn_if_default.c +++ b/rpn_if_default.c @@ -1,11 +1,200 @@ #include "rpn_if_default.h" +rpn_if_param_t* rpn_if_default_params(short pos_flag, short res_flag, + const size_t *lim, const rpn_value_t *res_const, + unsigned char rpn_stack_sz) +{ + rpn_if_param_t *res; + rpn_if_default_data_t *data; + size_t lim_sz, const_val_sz, param_sz, rpn_sz, mem_sz, ndim, value_sz, argc, i; -int rpn_if_argf_default(rpn_if_t *rif, size_t pos, rpn_value_t *args) + // Calculating full params + default_data + size_lim + const_val size + short lim_off = 0; + switch(pos_flag) + { + case RPN_IF_POSITION_LINEAR: + ndim = lim_sz = 1; + break; + case RPN_IF_POSITION_XY: + ndim = lim_sz = 2; + break; + case RPN_IF_POSITION_XDIM: + ndim = lim_sz = *lim; + lim_sz++; + lim_off = 1; + break; + default: + fprintf(stderr, + "Invalid position flag for if params : %d\n", + pos_flag); + return NULL; + } + mem_sz = 1; + for(i=0;idata = data; + + data->pos_flag = pos_flag; + data->res_flag = res_flag; + data->ndim = ndim; + data->size_lim = (size_t*)&(data[1]); + data->const_val = ((void*)data->size_lim) + lim_sz; + + memcpy(data->size_lim, lim, lim_sz); + + if(const_val_sz) + { + memcpy(data->const_val, res_const, const_val_sz); + } + else + { + data->const_val = NULL; + } + + res->getarg_f = rpn_if_getarg_default; + res->setres_f = rpn_if_setres_default; + + res->rpn_argc = argc; + res->rpn_stack_sz = rpn_stack_sz; + res->value_sz = value_sz; + res->mem_sz = mem_sz; + res->rpn_sz = rpn_sz; + + return res; +} + +int rpn_if_sizes_from_flag(short pos_flag, short res_flag, short sizes[2]) +{ + short *lim_sz = &sizes[0]; + short *const_sz = &sizes[1]; + + *lim_sz = *const_sz = -1; + + switch(pos_flag) + { + case RPN_IF_POSITION_LINEAR: + *lim_sz = 1; + break; + case RPN_IF_POSITION_XY: + *lim_sz = 2; + break; + case RPN_IF_POSITION_XDIM: + *lim_sz = 1; + break; + default: + return -1; + } + switch(res_flag) + { + case RPN_IF_RES_CONST: + *const_sz = 1; + break; + /* + case RPN_IF_RES_CONST_RGB: + *const_sz = 3; + break; + */ + case RPN_IF_RES_CONST_RGBA: + *const_sz = 4; + break; + case RPN_IF_RES_BOOL: + case RPN_IF_RES_COUNT: + case RPN_IF_RES_XFUN: + case RPN_IF_RES_RGB: + case RPN_IF_RES_RGBA: + *const_sz = 0; + break; + default: + return -1; + } + return 0; +} + +int rpn_if_getarg_default(rpn_if_t *rif, size_t pos) { size_t cur_arg, i, rgb_imax; rpn_if_default_data_t *data; rpn_value_t *values; + rpn_value_t *args = rif->rpn_args; data = (rpn_if_default_data_t*)rif->params->data; @@ -23,6 +212,8 @@ int rpn_if_argf_default(rpn_if_t *rif, size_t pos, rpn_value_t *args) rpn_if_argf_xdim(rif, pos, args); cur_arg = *(data->size_lim); break; + default: + return -1; } if(cur_arg > rif->params->rpn_argc) { @@ -62,7 +253,7 @@ int rpn_if_argf_default(rpn_if_t *rif, size_t pos, rpn_value_t *args) return -1; } -int rpn_if_resf_default(rpn_if_t *rif, size_t *pos, rpn_value_t *res) +int rpn_if_setres_default(rpn_if_t *rif, size_t *pos) { rpn_if_default_data_t *data; size_t cur_arg, i, rgb_imax; @@ -73,38 +264,42 @@ int rpn_if_resf_default(rpn_if_t *rif, size_t *pos, rpn_value_t *res) switch(data->pos_flag) { case RPN_IF_POSITION_LINEAR: - rpn_if_resf_linear(rif, pos, res); + rpn_if_resf_linear(rif, pos); cur_arg = 1; break; case RPN_IF_POSITION_XY: - rpn_if_resf_xy(rif, pos, res); + rpn_if_resf_xy(rif, pos); cur_arg = 2; break; case RPN_IF_POSITION_XDIM: - rpn_if_resf_xdim(rif, pos, res); + rpn_if_resf_xdim(rif, pos); cur_arg = *(data->size_lim); break; + default: + return -1; } if(cur_arg > rif->params->rpn_argc) { /** LOG ERROR ! should never append... */ return -1; } - rgb_imax = 3; /* rgba */ + rgb_imax = 3; /* rgb */ values = rpn_if_getitem(rif, *pos); - /**@todo if(res) set the values in res too ! */ switch(data->res_flag) { case RPN_IF_RES_BOOL: *values = 1; + *rif->rpn_res = *values; break; case RPN_IF_RES_CONST: *values = *(data->const_val); + *rif->rpn_res = *values; break; case RPN_IF_RES_COUNT: (*values)++; + *rif->rpn_res = *values; break; case RPN_IF_RES_CONST_RGBA: @@ -113,6 +308,7 @@ int rpn_if_resf_default(rpn_if_t *rif, size_t *pos, rpn_value_t *res) for(i=0;iconst_val[i]; + rif->rpn_res[i] = values[i]; } break; @@ -155,7 +351,7 @@ int rpn_if_argf_linear(rpn_if_t *rif, size_t pos, rpn_value_t *args) return 0; } -int rpn_if_resf_linear(rpn_if_t *rif, size_t *pos, rpn_value_t *_data) +int rpn_if_resf_linear(rpn_if_t *rif, size_t *pos) { rpn_if_default_data_t *data; size_t res; @@ -195,7 +391,7 @@ int rpn_if_argf_xy(rpn_if_t *rif, size_t pos, rpn_value_t *args) return 0; } -int rpn_if_resf_xy(rpn_if_t *rif, size_t *pos, rpn_value_t *_data) +int rpn_if_resf_xy(rpn_if_t *rif, size_t *pos) { rpn_if_default_data_t *data; size_t xy[2]; @@ -238,13 +434,12 @@ int rpn_if_argf_xdim(rpn_if_t *rif, size_t pos, rpn_value_t *args) { return -1; } - /**@todo check if *(data->size_lim) overflow rif->params->rpn_argc */ curdim_sz = 1; curpos = pos; for(i=0; i<*(data->size_lim)-1; i++) { - curdim_sz *= data->size_lim[i+1]; + curdim_sz = data->size_lim[i+1]; args[i] = curpos % curdim_sz; curpos /= curdim_sz; } @@ -260,10 +455,10 @@ int rpn_if_argf_xdim(rpn_if_t *rif, size_t pos, rpn_value_t *args) return 0; } -int rpn_if_resf_xdim(rpn_if_t *rif, size_t *pos, rpn_value_t *_data) +int rpn_if_resf_xdim(rpn_if_t *rif, size_t *pos) { rpn_if_default_data_t *data; - size_t i, res, cur, curlim, prevlim; + size_t i, res, cur, curlim, dim_sz; data = (rpn_if_default_data_t*)rif->params->data; res = 0; @@ -276,22 +471,11 @@ int rpn_if_resf_xdim(rpn_if_t *rif, size_t *pos, rpn_value_t *_data) { return -1; } - /**@todo check if *(data->size_lim) overflow rif->params->rpn_argc */ - res = rif->rpn_res[0]; - if(res >= data->size_lim[1]) - { - if(data->pos_flag & RPN_IF_POSITION_OF_ERR) - { - return -1; - } - res %= data->size_lim[1]; - } - - for(i=1; i < *(data->size_lim); i++) + dim_sz = 1; + for(i=0; i < *(data->size_lim); i++) { cur = rif->rpn_res[i]; - prevlim = data->size_lim[i]; curlim = data->size_lim[i+1]; if(cur >= curlim) { @@ -301,7 +485,8 @@ int rpn_if_resf_xdim(rpn_if_t *rif, size_t *pos, rpn_value_t *_data) } cur %= curlim; } - res += cur * prevlim; + res += cur * dim_sz; + dim_sz *= curlim; } *pos = res; return 0; diff --git a/rpn_if_default.h b/rpn_if_default.h index e72e4c4..ec32aef 100644 --- a/rpn_if_default.h +++ b/rpn_if_default.h @@ -18,6 +18,8 @@ */ #ifndef __rpn_if_default__h__ #define __rpn_if_default__h__ +#include "config.h" +#include "rpn_if.h" /**@file rpn_if_default.h Defines default IF * @ingroup ifs_if_default @@ -25,17 +27,19 @@ * @brief Default IF definitions */ -/**@defgroup ifs_if_default Default functions +/**@defgroup ifs_if_default Default iterated functions * @ingroup ifs_if - * @brief Simple iterated functions functions + * @brief Iterated function default implementation * - * Defines default @ref rpn_if_param_s.res_f and @ref rpn_if_param_s.arg_f - * functions. + * Defines default @ref rpn_if_param_s.setres_f and @ref rpn_if_param_s.getarg_f + * functions and flags to select them (see @ref ifs_if_default_posflag and + * @ref ifs_if_default_resflag ). + * + * The @ref rpn_if_default_params function constructs suitable + * @ref rpn_if_param_t to instanciate an @ref rpn_if_t with + * @ref rpn_if_new function. */ -#include "config.h" -#include "rpn_if.h" - /**@weakgroup ifs_if_default_posflag Default IF position flags * @ingroup ifs_if_default * @{ */ @@ -65,6 +69,7 @@ typedef struct rpn_if_default_data_s rpn_if_default_data_t; /**@brief Stores default IF data * * Stores flags and size limit + * @ingroup ifs_if_default */ struct rpn_if_default_data_s { @@ -77,84 +82,130 @@ struct rpn_if_default_data_s * @note If NULL no limit * - For @ref RPN_IF_POSITION_LINEAR size_lim is a single size_t * - For @ref RPN_IF_POSITION_XY size_lim is two size_t (height and width) - * - For @ref RPN_IF_POSITION_XDIM *size_lim is the size of size_lim + * - For @ref RPN_IF_POSITION_XDIM the first element (*size_lim) is the + * size of the size_lim array */ size_t *size_lim; - /**@brief Store constant values to set mem giver res_flag - * - For @ref RPN_IF_RES_CONST_RGBA const_val points on a single value + /** Number of dimensions (if XDIM ndim = len(size_lim)-1) */ + size_t ndim; + + /**@brief Store constant values to set mem given res_flag + * - For @ref RPN_IF_RES_CONST const_val points on a single value * - For @ref RPN_IF_RES_CONST_RGBA const_val points on 4 values + * - Else const_val is set to NULL */ rpn_value_t *const_val; }; -/**@brief Create a new @ref rpn_if_s corresponding to given flags +/**@brief Create a new @ref rpn_if_param_s corresponding to given flags * @ingroup ifs_if_default * * @param pos_flag Binary OR combination of RPN_IF_POSITION_* * (@ref ifs_if_default_posflag ) - * @param pos_flag Binary OR combination of RPN_IF_RES_* - * (@ref ifs_if_default_posflag ) - * @returns A new @ref rpn_if_t (see @ref rpn_if_new ) or NULL on - * error - * @todo Implementation/testing + * @param res_flag Binary OR combination of RPN_IF_RES_* + * (@ref ifs_if_default_resflag ) + * @param lim Depends on pos_flag parameter ( + * see @ref rpn_if_default_data_s::size_lim ) + * @param res_const Depends on res_flag parameter ( + * see @ref rpn_if_default_data_s::const_val ) + * @param rpn_stack_sz The size of the stack for expressions + * @returns A new @ref rpn_if_param_t or NULL on error + * @ingroup ifs_if_default */ -rpn_if_t* rpn_if_new_default(short pos_flag, short res_flag); +rpn_if_param_t* rpn_if_default_params(short pos_flag, short res_flag, + const size_t *lim, const rpn_value_t *res_const, + unsigned char rpn_stack_sz); -/**@brief Default argf function ( see @ref rpn_if_param_s.arg_f ) */ -int rpn_if_argf_default(rpn_if_t *rif, size_t pos, rpn_value_t *args); -/**@brief Default result function ( see @ref rpn_if_param_s.res_f ) */ -int rpn_if_resf_default(rpn_if_t *rif, size_t *pos, rpn_value_t *data); +/** Fetch size limit and const values array sizes given flag values + * @param pos_flag (@ref ifs_if_default_posflag) + * @param res_flag (@ref ifs_if_default_resflag) + * @param sizes size limit array size and constant values array size + * @return 0 or -1 if a flag is not valid + * @warning returns 1 for size limit when XDIM position, but actually the + * limit is given by the 1st number in the limit (example : [2,640,480], + * [3,16,640,480], ...) + * @todo replace short by int for sizes + */ +int rpn_if_sizes_from_flag(short pos_flag, short res_flag, short sizes[2]); -/**@brief Set the first expression argument from position +/**@brief Default argf function ( see @ref rpn_if_param_s.getarg_f ) + * + * This function handle internal rif modification to set next arguments. + * It will call specialized function depending on + * @ref rpn_if_default_data_s.pos_flag and @ref rpn_if_default_data_s.res_flag + * @note The position is the first set of arguments and can be use to + * look for the other one + * + * @param rif Pointer on expressions + * @param pos The position + * @return 0 or -1 on error + */ +int rpn_if_getarg_default(rpn_if_t *rif, size_t pos); + +/**@brief Default result function ( see @ref rpn_if_param_s.setres_f ) + * + * This function will store the result at given position and + * It will call specialized function depending on + * @ref rpn_if_default_data_s.pos_flag and @ref rpn_if_default_data_s.res_flag + * @param rif Pointer on expressions + * @param pos Will be set to the resulting position + * @return 0 or -1 on error + */ +int rpn_if_setres_default(rpn_if_t *rif, size_t *pos); + +/**@brief Set the first expression argument from linear position * @param rif Expressions * @param pos Memory map offset * @param args pointer on expressions arguments - * @note Other arguments are set using the generic @ref rpn_if_argf_default + * @return 0 or -1 on error + * @note Other arguments are set using the generic @ref rpn_if_getarg_default * function */ int rpn_if_argf_linear(rpn_if_t *rif, size_t pos, rpn_value_t *args); /**@brief Transform 1st expression result to position * @param rif Expressions * @param pos Pointer on resulting position in memory map - * @param data Pointer on resulting data - * @note Data from position fecth is done by generic @ref rpn_if_resf_default + * @return 0 or -1 on error + * @note Data from position fecth is done by generic @ref rpn_if_setres_default * function */ -int rpn_if_resf_linear(rpn_if_t *rif, size_t *pos, rpn_value_t *data); +int rpn_if_resf_linear(rpn_if_t *rif, size_t *pos); /**@brief Set the 1st & 2nd argument from position * @param rif Expressions * @param pos Memory map offset * @param args pointer on expression arguments - * @note Other arguments are set using the generic @ref rpn_if_argf_default + * @return 0 or -1 on error + * @note Other arguments are set using the generic @ref rpn_if_getarg_default * function */ int rpn_if_argf_xy(rpn_if_t *rif, size_t pos, rpn_value_t *args); /**@brief Transform 1st and 2nd result into a memory map's offset * @param rif Expressions * @param pos Memory map offset pointer - * @param data Pointer on resulting data - * @note Data from position fetch is done by generic @ref rpn_if_resf_default + * @return 0 or -1 on error + * @note Data from position fetch is done by generic @ref rpn_if_setres_default * function */ -int rpn_if_resf_xy(rpn_if_t *rif, size_t *pos, rpn_value_t *data); +int rpn_if_resf_xy(rpn_if_t *rif, size_t *pos); /**@brief Set X first arguments from position * @param rif Expressions * @param pos Memory map offset * @param args Pointer on expression arguments - * @note Other arguments are set using the generic @ref rpn_if_argf_default + * @return 0 or -1 on error + * @note Other arguments are set using the generic @ref rpn_if_getarg_default * function */ int rpn_if_argf_xdim(rpn_if_t *rif, size_t pos, rpn_value_t *args); /**@brief Transform X arguments into a memory map's offset * @param rif Expressions * @param pos Memory map offset pointer - * @param data Pointer on resulting data - * @note Data from position fetch is done by generic @ref rpn_if_resf_default + * @return 0 or -1 on error + * @note Data from position fetch is done by generic @ref rpn_if_setres_default * function */ -int rpn_if_resf_xdim(rpn_if_t *rif, size_t *pos, rpn_value_t *data); +int rpn_if_resf_xdim(rpn_if_t *rif, size_t *pos); #endif diff --git a/rpn_if_mutate.c b/rpn_if_mutate.c new file mode 100644 index 0000000..43a687e --- /dev/null +++ b/rpn_if_mutate.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020,2023 Weber Yann + * + * This file is part of pyrpn. + * + * pyrpn is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * pyrpn is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with pyrpn. If not, see . + */ + +#include "rpn_if_mutate.h" + + +int if_mutation(rpn_if_t *rif, rnd_t *weights, size_t n_mut, + rpn_mutation_params_t *mut_params) +{ + for(size_t i=0; iparams->rpn_sz, weights, + &rpn_num) < 0) + { + return -1; + } + if(rpn_mutation(&(rif->rpn[rpn_num].toks), + mut_params) < 0) + { + return -1; + } + } + for(size_t i=0; iparams->rpn_sz; i++) + { + rpn_expr_tokens_updated(&(rif->rpn[i])); + } + return 0; +} diff --git a/rpn_if_mutate.h b/rpn_if_mutate.h new file mode 100644 index 0000000..61f56a4 --- /dev/null +++ b/rpn_if_mutate.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020,2023 Weber Yann + * + * This file is part of pyrpn. + * + * pyrpn is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * pyrpn is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with pyrpn. If not, see . + */ +#ifndef __rpn_if_mutate__h__ +#define __rpn_if_mutate__h__ +#include "config.h" + +#include +#include +#include +#include + +#include "rpn_mutate.h" +#include "rpn_if.h" + + +/**@file rpn_if_mutate.h + * @brief rpn_if_t mutation headers + */ + +/**@brief IF mutation weight type */ +typedef float if_mutation_weight_t; + +/**@brief Allocate some weights for an IF + * @param rif rpn_if_t to allocate weights for + * @param to_alloc is a **SOMETYPE, *to_alloc will be set to the + * weights array + * @return NULL on error */ +#define if_mutation_alloc_weights(rif, to_alloc) (*to_alloc = \ + malloc(sizeof(**to_alloc)*rif->params->rpn_sz)) + + +/**@brief Mutate the IF given weights array + * @param if the IF we want to mutate + * @param weights Array of weights initialized by + * @ref rpnifs_fast_rnd_weights() + * @param n_mut Number of mutation to execute + * @param mut_params Mutation parameters for IF expressions + * @return 0 if no error else -1 and sets errno */ +int if_mutation(rpn_if_t *rif, rnd_t *weights, size_t n_mut, + rpn_mutation_params_t *mut_params); + +#endif diff --git a/rpn_ifs.c b/rpn_ifs.c index 31d018b..c287148 100644 --- a/rpn_ifs.c +++ b/rpn_ifs.c @@ -7,6 +7,7 @@ rpn_ifs_t* rpn_ifs_new(rpn_if_param_t *params, rpn_value_t *memmap) if(!(res = malloc(sizeof(rpn_ifs_t)))) { + err = errno; goto error; } bzero(res, sizeof(rpn_ifs_t)); @@ -25,6 +26,7 @@ rpn_ifs_t* rpn_ifs_new(rpn_if_param_t *params, rpn_value_t *memmap) -1, 0); if(res->mem == (void*)-1) { + err = errno; goto mmap_err; } } @@ -32,10 +34,8 @@ rpn_ifs_t* rpn_ifs_new(rpn_if_param_t *params, rpn_value_t *memmap) return res; mmap_err: - err = errno; free(res); error: - err = errno; errno = err; return NULL; } @@ -60,6 +60,72 @@ void rpn_ifs_free(rpn_ifs_t *rifs) } } +size_t rpn_ifs_set_if_count(rpn_ifs_t *rifs, size_t count, unsigned int *weights) +{ + errno = 0; + + const size_t old_sz = rifs->if_sz; + + // free old functions if old_sz > count + if(old_sz) + { + for(size_t i=old_sz-1; i>=count; i--) + { + rpn_if_free(rifs->rpn_if[i]); + } + } + + void *tmp; + + if(!(tmp = realloc(rifs->rpn_if, sizeof(rpn_if_t*)*count))) + { + return 0; + } + rifs->rpn_if = tmp; + + if(!(tmp = realloc(rifs->weight, sizeof(unsigned int) * count))) + { + return 0; + } + rifs->weight = tmp; + + rifs->flat_sz = rifs->params.rpn_sz * count; + if(!(tmp = realloc(rifs->flat_rpn, sizeof(rpn_expr_t*) * rifs->flat_sz))) + { + rifs->flat_sz = rifs->params.rpn_sz * old_sz; + return 0; + } + rifs->flat_rpn = tmp; + rifs->if_sz = count; + + + // init new functions if old_sz < count + for(size_t i=old_sz; irpn_if[i] = rpn_if_new(&(rifs->params), rifs->mem, NULL); + if(!rifs->rpn_if[i]) + { + return 0; + } + for(size_t j=0; jparams.rpn_sz; j++) + { + const size_t flat_idx = (i*rifs->params.rpn_sz) + j; + rifs->flat_rpn[flat_idx] = &(rifs->rpn_if[i]->rpn[j]); + } + } + + + + // set all weights + memcpy(rifs->weight, weights, sizeof(unsigned int) * count); + + if(rpn_ifs_weight_update(rifs) < 0) + { + return 0; + } + return rifs->if_sz; +} + size_t rpn_ifs_add_if(rpn_ifs_t *rifs, unsigned int weight) { size_t res, i, first_flat; @@ -81,7 +147,7 @@ size_t rpn_ifs_add_if(rpn_ifs_t *rifs, unsigned int weight) rifs->weight[rifs->if_sz] = weight; //WRONG expr ARGUMENT !!! - rifs->rpn_if[rifs->if_sz] = rpn_if_new(&(rifs->params), rifs->mem); + rifs->rpn_if[rifs->if_sz] = rpn_if_new(&(rifs->params), rifs->mem, NULL); if(!rifs->rpn_if[rifs->if_sz]) { return 0; @@ -101,7 +167,7 @@ size_t rpn_ifs_add_if(rpn_ifs_t *rifs, unsigned int weight) rifs->if_sz++; if(rpn_ifs_weight_update(rifs) < 0) { - rpn_ifs_del_if(rifs, res); // don't attempt to ceck for errors.. + rpn_ifs_del_if(rifs, res); // don't attempt to check for errors.. return 0; } return res; @@ -132,8 +198,34 @@ int rpn_ifs_del_if(rpn_ifs_t *rifs, size_t if_idx) return 0; } -int rpn_ifs_run(rpn_ifs_t *rifs, size_t n) +int rpn_ifs_run(rpn_ifs_t *rifs, size_t n, unsigned char *rnd) { + unsigned char *_rnd; + if(!rnd) + { + if(!(_rnd = malloc(sizeof(unsigned char)*n))) + { + return -1; + } + if(getrandom(_rnd, n, GRND_NONBLOCK) < 0) + { + int err = errno; + free(_rnd); + errno = err; + return -1; + } + } + else + { + _rnd = rnd; + } + + for(size_t i=0; iif_proba[_rnd[i]]; + assert(cur_if != NULL); + rifs->pos = rpn_if_step(cur_if, rifs->pos); + } return 0; } @@ -144,7 +236,7 @@ int rpn_ifs_weight_update(rpn_ifs_t *rifs) rpn_if_t **proba; proba = rifs->if_proba; - bzero(rifs->if_proba, sizeof(rpn_if_t*)*255); + bzero(rifs->if_proba, sizeof(*rifs->if_proba)); weight_sum = 0; @@ -155,14 +247,22 @@ int rpn_ifs_weight_update(rpn_ifs_t *rifs) j=0; for(i=0; i < rifs->if_sz; i++) { - max = rifs->weight[i] * 255 / weight_sum; - while(max) + max = rifs->weight[i] * 256 / weight_sum; + while(max && j < 256) { - *proba = rifs->rpn_if[i]; + proba[j] = rifs->rpn_if[i]; max--; j++; } } + for(j=j; j<256; j++) + { + // dirty & quick fill with last value + /* @todo enhance the random choice resolution to match + * more closely asked weights (uint16 vs actualt uint8 random + * choice ? */ + rifs->if_proba[j] = rifs->if_proba[j-1]; + } return 0; } @@ -171,8 +271,10 @@ rpn_expr_t **rpn_ifs_flat_rpn(rpn_ifs_t *rifs) return NULL; } -int rpn_ifs_step(rpn_ifs_t *rifs) +int rpn_ifs_step(rpn_ifs_t *rifs, unsigned char rnd) { - return 1; + rpn_if_t *cur_if = rifs->if_proba[rnd]; + rifs->pos = rpn_if_step(cur_if, rifs->pos); + return 0; } diff --git a/rpn_ifs.h b/rpn_ifs.h index c1fb270..9d31371 100644 --- a/rpn_ifs.h +++ b/rpn_ifs.h @@ -21,11 +21,17 @@ #include "config.h" +#include #include #include "rpn_jit.h" #include "rpn_if.h" +/**@file rpn_ifs.h + * @brief IFS header + * @ingroup ifs + */ + /**@defgroup ifs Iterated function system * @brief IFS implementation with RPN expressions * @@ -64,9 +70,10 @@ struct rpn_ifs_s /**@brief Stores the original chance of choosing corresponding IF */ unsigned int *weight; - /** @brief Stores an array of 255 pointers on IF allowing fast - * random choice. Last ptr can be NULL*/ - rpn_if_t *if_proba[255]; + /** @brief Stores an array of 256 pointers on IF allowing fast + * random choice + * @todo replace with rpnifs_random_*functions */ + rpn_if_t *if_proba[256]; /**@brief Stores the RPN expressions pointer of the IF contained in * the system */ @@ -90,6 +97,15 @@ rpn_ifs_t* rpn_ifs_new(rpn_if_param_t *params, rpn_value_t *memmap); */ void rpn_ifs_free(rpn_ifs_t *rifs); +/**@brief Set the number and weights of functions in the system + * @note Do not re-init existing function + * @param rifs The iterated function system + * @param count The number of functions in the system + * @param weights The function's weight + * @return The number of function in the system + */ +size_t rpn_ifs_set_if_count(rpn_ifs_t *rifs, size_t count, unsigned int *weights); + /**@brief Add a new iterated function to the system * @param rifs The iterated function system * @param weight The new expression weight @@ -111,9 +127,10 @@ int rpn_ifs_del_if(rpn_ifs_t *rifs, size_t if_idx); * @note Make n random choices and call corresponding IF * @param rifs The iterated function system * @param n consecutive IFS calls - * @return 1 if error else 0 + * @param seed prepopulated array of random bytes (if NULL one is generated) + * @return -1 if error else 0 */ -int rpn_ifs_run(rpn_ifs_t *rifs, size_t n); +int rpn_ifs_run(rpn_ifs_t *rifs, size_t n, unsigned char *rnd); /**@brief Updates the @ref rpn_ifs_s.if_proba array using * @ref rpn_ifs_s.if_proba values @@ -130,9 +147,11 @@ int rpn_ifs_weight_update(rpn_ifs_t *rifs); rpn_expr_t **rpn_ifs_flat_rpn(rpn_ifs_t *rifs); /**@brief Randomly choose an IF and make a step updating ifs current posisition + * @param rifs The IF system + * @param seed A random byte * @return -1 on error else 0 */ -int rpn_ifs_step(rpn_ifs_t *rifs); +int rpn_ifs_step(rpn_ifs_t *rifs, unsigned char rnd); #endif diff --git a/rpn_ifs_mutate.c b/rpn_ifs_mutate.c new file mode 100644 index 0000000..35b2c20 --- /dev/null +++ b/rpn_ifs_mutate.c @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2020 Weber Yann + * + * This file is part of pyrpn. + * + * pyrpn is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * pyrpn is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with pyrpn. If not, see . + */ + +#include "rpn_ifs_mutate.h" + +int ifs_mutation_weights_alloc(ifs_mutation_weights_t *res, + rpn_ifs_t *ifs, short custom_params, short custom_comp_if) +{ +#if DEBUG + if(!res || !ifs) + { + errno = EINVAL; + return -1; + } +#endif + res->if_count = ifs->if_sz; + res->w_mut_if = malloc(sizeof(*res->w_mut_if)*ifs->if_sz); + if(!res->w_mut_if) { goto err; } + + res->_w_if = malloc(sizeof(*res->_w_if)*ifs->if_sz); + if(!res->_w_if) { goto err_w_if; } + + res->w_comp_if_sz = ifs->params.rpn_sz; + if(custom_comp_if) + { + res->w_comp_if_sz *= ifs->if_sz; + } + res->w_comp_if = malloc(sizeof(*res->w_comp_if)*res->w_comp_if_sz); + if(!res->w_comp_if) + { + goto err_comp_if; + } + res->_w_comp_if = malloc(sizeof(*res->_w_comp_if)*res->w_comp_if_sz); + if(!res->_w_comp_if) + { + goto err__comp_if; + } + + size_t p_sz = sizeof(*res->if_mut_params); + p_sz *= custom_params?res->if_count:1; + res->if_mut_params = malloc(p_sz); + if(!res->if_mut_params) { goto err_if_mut_params; } + bzero(res->if_mut_params, p_sz); + + return 0; + +err_if_mut_params: + free(res->_w_comp_if); +err__comp_if: + free(res->w_comp_if); + res->w_comp_if_sz = 0; +err_comp_if: + free(res->_w_if); +err_w_if: + free(res->w_mut_if); +err: + return -1; +} + +int ifs_mutation_weights_if_count_update(ifs_mutation_weights_t *res, + size_t new_if_count) +{ +#ifndef NDEBUG + if(!res) + { + errno=EINVAL; + return -1; + } +#endif + if(new_if_count == 0) + { + res->if_count = 0; + ifs_mutation_weights_dealloc(res); + res->w_mut_if = NULL; + res->_w_if = NULL; + res->if_mut_params = NULL; + return 0; + } + else if(new_if_count == res->if_count) + { + return 0; + } + + ifs_mutation_weights_t tmp; + + tmp.w_mut_if = realloc(res->w_mut_if, + sizeof(*res->w_mut_if)*new_if_count); + if(!tmp.w_mut_if) { goto err; } + res->w_mut_if = tmp.w_mut_if; + + if(res->if_count < new_if_count) + { + // When needed new weights are weights average + double sum = 0.0; + for(size_t i=0; iif_count; i++) { sum += res->w_mut_if[i]; } + sum = sum == 0.0 ? 1.0 : sum; + for(size_t i=res->if_count; iif_count != 0) + { + res->w_mut_if[i] = sum / res->if_count; + } + else + { + res->w_mut_if[i] = sum / new_if_count; + } + } + } + + tmp._w_if = realloc(res->_w_if, + sizeof(*res->_w_if) * new_if_count); + if(!tmp._w_if) { goto err; } + res->_w_if = tmp._w_if; + + if(res->custom_params) + { + tmp.if_mut_params = realloc(res->if_mut_params, + sizeof(*res->if_mut_params)*new_if_count); + if(!tmp.if_mut_params) + { + goto err; + } + res->if_mut_params = tmp.if_mut_params; + + // mutations parameters should be allready initialized + // and are not reinit after copy + if(res->if_count == 0) + { + // adds default mutation parameters + memcpy(res->if_mut_params, + &rpn_mutation_params_default, + sizeof(*res->if_mut_params)); + } + const size_t start = res->if_count > 0 ? res->if_count : 1; + for(size_t i=start; iif_mut_params[i]), + &(res->if_mut_params[i-1]), + sizeof(*res->if_mut_params)); + } + } + + res->if_count = new_if_count; + + return ifs_mutation_weights_update(res); +err: + return -1; +} + +void ifs_mutation_weights_dealloc(ifs_mutation_weights_t *w) +{ + if(w->w_mut_if) { free(w->w_mut_if); } + if(w->_w_if) { free(w->_w_if); } + if(w->if_mut_params) { free(w->if_mut_params); } +} + +int ifs_mutation_weights_update(ifs_mutation_weights_t *w) +{ + if(w->if_count == 0) { return 0; } +#if DEBUG + if(w->w_mut_if == NULL) + { errno = EINVAL; return -1; } + if(w->_w_if == NULL) { errno = EINVAL; return -1; } + if(w->_w_comp_if == NULL) { errno = EINVAL; return -1; } +#endif + if(rpnifs_fast_rnd_weights(2, w->w_mut_type, w->_w_type) < 0) + { + return -1; + } + if(w->custom_params) + { + for(size_t i=0; i < w->if_count; i++) + { + if(rpn_mutation_init_params(&(w->if_mut_params[i])) < 0) + { + return -1; + } + } + } + else + { + if(rpn_mutation_init_params(w->if_mut_params) < 0) + { + return -1; + } + } + + if(rpnifs_fast_rnd_weights(w->if_count, w->w_mut_if, w->_w_if) < 0) + { + goto err; + } + + if(rpnifs_fast_rnd_weights(w->w_comp_if_sz, w->w_comp_if, + w->_w_comp_if) < 0) + { + goto err; + } + return 0; +err: + return -1; +} + + +int ifs_mutation(rpn_ifs_t *ifs, ifs_mutation_weights_t *w, size_t n_mut) +{ + size_t choices[2] = {0, 0}; + size_t ret; + + for(size_t i=0; i_w_type, &ret) < 0) + { + return -1; + } + choices[ret]++; + } + if(choices[0]) + { + if(ifs_weight_mutation(ifs, w, choices[0]) < 0) + { + return -1; + } + } + if(choices[1]) + { + if(ifs_if_mutation(ifs, w, choices[1]) < 0) + { + return -1; + } + } + + return 0; +} + +int ifs_weight_mutation(rpn_ifs_t *ifs, ifs_mutation_weights_t *w, size_t n_mut) +{ + for(size_t i=0; iif_sz, NULL, &ret) < 0) + { + return -1; + } + float mod; + if(rpnifs_rnd_float(w->w_weight_range, &mod) < 0) + { + return -1; + } + ifs->weight[ret] += mod; + } + return 0; +} + +int ifs_if_mutation(rpn_ifs_t *ifs, ifs_mutation_weights_t *w, size_t n_mut) +{ + size_t *muts; + + if(!(muts = malloc(sizeof(*muts)*ifs->if_sz))) + { + return -1; + } + bzero(muts, sizeof(*muts)*ifs->if_sz); + + for(size_t i=0; iif_sz, w->_w_if, &ret) < 0) + { + goto err; + } + muts[ret]++; + } + for(size_t i=0; iw_comp_if_sz == ifs->if_sz) + { + weights = w->_w_comp_if; + } + else + { + weights = &(w->_w_comp_if[i*ifs->if_sz]); + } + rpn_mutation_params_t *params; + if(w->custom_params) + { + params = &(w->if_mut_params[i]); + } + else + { + params = w->if_mut_params; + } + if(if_mutation(ifs->rpn_if[i], weights, muts[i], params) < 0) + { + return -1; + } + } + free(muts); + return 0; +err: + free(muts); + return -1; +} diff --git a/rpn_ifs_mutate.h b/rpn_ifs_mutate.h new file mode 100644 index 0000000..22ca7f3 --- /dev/null +++ b/rpn_ifs_mutate.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2020,2023 Weber Yann + * + * This file is part of pyrpn. + * + * pyrpn is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * pyrpn is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with pyrpn. If not, see . + */ +#ifndef __rpn_ifs_mutate__h__ +#define __rpn_ifs_mutate__h__ +#include "config.h" + +#include +#include +#include +#include + +#include "rpn_mutate.h" +#include "rpn_if_mutate.h" +#include "rpn_ifs.h" + + +/**@file rpn_ifs_mutate.h + * @brief rpn_ifs_t mutation headers + * @todo implement function to mutate IF (in another file ? : yep) + */ + + +/**@brief IFS mutation parameters */ +typedef struct ifs_mutation_weights_s ifs_mutation_weights_t; + +/**@brief IFS mutation parameters + * @todo add weights for mutations of IF (weights for component mutation in IF) + */ +struct ifs_mutation_weights_s +{ + /**@brief Weight for mutation type : expr, weight */ + float w_mut_type[2]; + + /**@brief internal use fast random choice for mutation type */ + rnd_t _w_type[2]; + + /**@brief Minimum & maximum weight variation (considering weights + * in [0.0 .. 1.0] and sum(weights) == 1.0 */ + float w_weight_range[2]; + + /**@brief Number of IF in the system */ + size_t if_count; + + /**@brief Weight for each IF (chance to mutate) */ + float *w_mut_if; + + /**@brief internal use fast random choice for if mutation */ + rnd_t *_w_if; + + /**@brief IF component mutation weights */ + if_mutation_weight_t *w_comp_if; + rnd_t *_w_comp_if; + /**@brief Can be if->rpn_sz (number of component in an IF) or + * if_count * if->rpn_sz (one weight for each component of each IF) */ + size_t w_comp_if_sz; + + /**@brief If 1 if_mut_params will countain if_count params else 1 */ + short custom_params; + + /**@brief Optionnal (or NULL) array for custom parameters for + * each IF */ + rpn_mutation_params_t *if_mut_params; + + +}; + +/**@brief Allocate internal fields according to the number of IF in the + * system + * @param res The struct to allocate in + * @param sz The number of if in the system + * @param custom_params If 0 do not allocate + * @ref ifs_mutation_weights_s::if_mut_params + * @param custom_comp_if Determine the value in + * @ref ifs_mutation_weights_s::w_comp_if_sz. If 0 allocate + * one weight per IF component, else allocate one weight for + * each component in each IF. + * @return 0 if no error else -1 and sets errno */ +int ifs_mutation_weights_alloc(ifs_mutation_weights_t *res, + rpn_ifs_t *ifs, short custom_params, short custom_comp_if); + +/**@brief When needed reallocate internal fields to match the number of IF in + * the system. + * @note Weights added by reallocation will be the weights average + * @note New if_mut_params added by reallocation will be copies of previous + * mutation params + * @param res The struct to reallocate in + * @param if_count The new IF count to addapt to + * @return 0 if no error else -1 and sets errno */ +int ifs_mutation_weights_if_count_update(ifs_mutation_weights_t *res, + size_t new_if_count); + +/**@brief Deallocate internal fields + * @param res The struct to cleanup */ +void ifs_mutation_weights_dealloc(ifs_mutation_weights_t *w); + +/**@brief Once allocate and all values set use this function to + * update internal fast random indexes + * @param w the ifs weights to update + * @return 0 if no error else -1 and sets errno */ +int ifs_mutation_weights_update(ifs_mutation_weights_t *w); + +/**@brief Use updated weights to mutate an IFS + * @param ifs The system to mutate + * @param w The mutation weights & parameters + * @param n_mut The number of mutations + * @param default_mut_params If custom + * @ref ifs_mutation_weights_s::if_mut_params params for each IF in + * the system not set, this argument must be not NULL and is used + * as IF mutation parameter + */ +int ifs_mutation(rpn_ifs_t *ifs, ifs_mutation_weights_t *w, size_t n_mut); + +int ifs_weight_mutation(rpn_ifs_t *ifs, ifs_mutation_weights_t *w, size_t n_mut); + +int ifs_if_mutation(rpn_ifs_t *ifs, ifs_mutation_weights_t *w, size_t n_mut); + +#endif + diff --git a/rpn_jit.c b/rpn_jit.c index bc0d73c..d858a55 100644 --- a/rpn_jit.c +++ b/rpn_jit.c @@ -68,7 +68,13 @@ int rpn_expr_reinit(rpn_expr_t* expr) return -1; } #endif - bzero(expr->code_map, expr->code_map_sz); + if(_rpn_expr_reset_map(expr) < 0) + { + snprintf(expr->err_reason, 128, + "Unable to re-init code map : %s", strerror(errno)); + expr->state = RPN_ERROR; + return -1; + } bzero(expr->stack, sizeof(unsigned long) * expr->stack_sz); if(_rpn_expr_init_map(expr) < 0) { @@ -81,6 +87,37 @@ int rpn_expr_reinit(rpn_expr_t* expr) return 0; } + +int rpn_expr_recompile(rpn_expr_t *expr, const char *code) +{ + if(rpn_expr_reinit(expr) < 0) + { + return -1; + } + return rpn_expr_compile(expr, code); +} + +int rpn_expr_tokens_updated(rpn_expr_t* expr) +{ + if(rpn_expr_reinit(expr) < 0) + { + return -1; + } + if(_rpn_expr_compile_tokens(expr) < 0) + { + expr->state = RPN_ERROR; + return -1; + } + if(expr->expr) + { + free(expr->expr); + } + expr->expr = rpn_tokenized_expr(&expr->toks, 0); + expr->state = RPN_READY; + return 0; +} + + int rpn_expr_compile(rpn_expr_t *expr, const char *code) { #ifdef DEBUG @@ -102,7 +139,7 @@ int rpn_expr_compile(rpn_expr_t *expr, const char *code) return _rpn_expr_compile_expr(expr); } -int rpn_expr_untokenize(rpn_expr_t *expr, rpn_tokenized_t *tokens, char long_op) +int rpn_expr_untokenize(rpn_expr_t *expr, const rpn_tokenized_t *tokens, char long_op) { int err; size_t i; @@ -139,7 +176,7 @@ int rpn_expr_untokenize(rpn_expr_t *expr, rpn_tokenized_t *tokens, char long_op) if(_rpn_expr_token_copy(expr, &(tokens->tokens[i])) < 0) { err = errno; - if(errno == EINVAL) + if(errno == EUCLEAN) { dprintf(2, "Fatal error, unknown token type : %d.\nMemory corruption ?\n", @@ -173,6 +210,8 @@ int rpn_expr_untokenize(rpn_expr_t *expr, rpn_tokenized_t *tokens, char long_op) char* rpn_random(size_t op_sz, size_t args_count) { + const int BUFF_ALLOC = 4096; + double step; size_t i, buff_sz, offset, rnd; char *buff, *cur; @@ -195,9 +234,9 @@ char* rpn_random(size_t op_sz, size_t args_count) for(i=0; itoks.tokens_sz) + \ + (sizeof(rpn_value_t)*expr->stack_sz); + + if(total_sz < buf_sz || !buf) + { + return total_sz; + } + + bzero(buf, total_sz); + + rpn_expr_serial_t *ser = buf; + rpn_token_t *tokens = (void*)buf + sizeof(*ser); + rpn_value_t *stack = (void*)(tokens + expr->toks.tokens_sz); + + ser->token_sz = expr->toks.tokens_sz; + ser->stack_sz = expr->stack_sz; + ser->argc = expr->toks.argc; + ser->state = expr->state; + memcpy(ser->err_reason, expr->err_reason, sizeof(ser->err_reason)); + memcpy(stack, expr->stack, expr->stack_sz); + + for(size_t i=0; i < ser->token_sz; i++) + { + tokens[i].type = expr->toks.tokens[i].type; + switch(expr->toks.tokens[i].type) + { + case RPN_arg: + tokens[i].arg_n = expr->toks.tokens[i].arg_n; + break; + case RPN_val: + tokens[i].value = expr->toks.tokens[i].value; + break; + case RPN_op: + tokens[i].op_n = expr->toks.tokens[i].op_n; + break; + default: + // SHOULD NEVER APPEND ! + errno = EINVAL; + goto err; + } + } + + return total_sz; + +err: + return 0; +} + + +int rpn_expr_deserialize(rpn_expr_t* expr, const void *buf, size_t buf_sz) +{ + int err = EINVAL; + const rpn_expr_serial_t *ser = buf; + + if(!expr) + { + errno = EINVAL; + return -1; + } + + if(buf_sz < sizeof(rpn_expr_serial_t)) + { + snprintf(expr->err_reason, 128, + "Given buffer is to small (%ld bytes) to deserialize", buf_sz); + err = EINVAL; + goto err; + } + + const size_t total_sz = sizeof(rpn_expr_serial_t) + \ + (sizeof(rpn_token_t)*ser->token_sz) + \ + (sizeof(rpn_value_t)*ser->stack_sz); + if(buf_sz < total_sz) + { + snprintf(expr->err_reason, 128, + "Expected %ld bytes but %ld given", + total_sz, buf_sz); + err = EINVAL; + goto err; + } + + rpn_token_t *tokens = (void*)buf + sizeof(*ser); + rpn_value_t *stack = (void*)(tokens + ser->token_sz); + + if(rpn_expr_init(expr, ser->stack_sz, ser->argc) < 0) + { + err = EINVAL; + goto err; + } + + expr->state = ser->state; + memcpy(expr->err_reason, ser->err_reason, 128); + memcpy(expr->stack, stack, sizeof(rpn_value_t)*ser->stack_sz); + + expr->args_count = expr->toks.argc = ser->argc; + + rpn_tokenized_t toks; + toks.argc = ser->argc; + toks.tokens_sz = ser->token_sz; + toks.tokens = malloc(sizeof(rpn_token_t)*ser->token_sz); + if(!toks.tokens) + { + err = errno; + snprintf(expr->err_reason, 128, + "Unable to allocate tokens : %s", + strerror(errno)); + goto err_expr; + } + + + for(size_t i=0; i < ser->token_sz; i++) + { + toks.tokens[i].type = tokens[i].type; + switch(tokens[i].type) + { + case RPN_val: + toks.tokens[i].value = tokens[i].value; + break; + case RPN_arg: + toks.tokens[i].arg_n = tokens[i].arg_n; + break; + case RPN_op: + toks.tokens[i].op_n = tokens[i].op_n; + toks.tokens[i].op = &(rpn_ops[tokens[i].op_n]); + break; + default: + snprintf(expr->err_reason, 128, + "Invalid token type encountered %d", tokens[i].type); + err = EINVAL; + goto err_expr; + } + } + + if(rpn_expr_untokenize(expr, &toks, 0) < 0) + { + err = EINVAL; + goto err_expr; + } + + expr->toks = toks; + + return 0; + +err_expr: + rpn_expr_close(expr); +err: + expr->state = RPN_ERROR; + errno = err; + return -1; +} + + int _rpn_expr_compile_expr(rpn_expr_t* expr) { rpn_tokenizer_t tokenizer; @@ -280,7 +476,7 @@ int _rpn_expr_compile_expr(rpn_expr_t* expr) { if(_rpn_expr_token_copy(expr, token) < 0) { - if(errno == EINVAL) + if(errno == EUCLEAN) { dprintf(2, "Fatal error, unknown token type : %d chr %ld.\nMemory corruption ?\n", @@ -326,7 +522,7 @@ int _rpn_expr_compile_tokens(rpn_expr_t* expr) token = &(expr->toks.tokens[i]); if(_rpn_expr_token_copy(expr, token) < 0) { - if(errno == EINVAL) + if(errno == EUCLEAN) { dprintf(2, "Fatal error, unknown token type : %d\nMemory corruption ?\n", @@ -358,7 +554,7 @@ int _rpn_expr_compile_tokens(rpn_expr_t* expr) unsigned long rpn_expr_eval(rpn_expr_t *expr, unsigned long *args) { rpn_run_f expr_run; - unsigned long int res; + rpn_value_t res; if(expr->state == RPN_ERROR) { return 0; @@ -407,6 +603,11 @@ int _rpn_expr_token_copy(rpn_expr_t *expr, rpn_token_t *token) value = NULL; break; case RPN_arg: + if(expr->args_count <= token->arg_n) + { + errno = EINVAL; + return -1; + } local_op.fun = &rpn_arg; local_op.fun_sz = &(CODE_SZ(rpn_arg)); value = &(token->arg_n); @@ -417,7 +618,7 @@ int _rpn_expr_token_copy(rpn_expr_t *expr, rpn_token_t *token) value = &(token->value); break; default: - errno = EINVAL; + errno = EUCLEAN; return -1; } if(_rpn_code_part_cpy(expr, local_op.fun, *(local_op.fun_sz), @@ -500,3 +701,18 @@ int _rpn_expr_end_map(rpn_expr_t *expr) return 0; } +int _rpn_expr_reset_map(rpn_expr_t *expr) +{ + if(!expr->code_map_sz) + { + return _rpn_expr_init_map(expr); + } + if(mprotect(expr->code_map, expr->code_map_sz, + PROT_READ | PROT_WRITE)) + { + return -1; + } + bzero(expr->code_map, expr->code_map_sz); + return 0; +} + diff --git a/rpn_jit.h b/rpn_jit.h index ea0d88d..6277efb 100644 --- a/rpn_jit.h +++ b/rpn_jit.h @@ -100,6 +100,9 @@ typedef unsigned long (*rpn_run_f)(unsigned long, unsigned long*, void*); * @ingroup rpn */ typedef struct rpn_expr_s rpn_expr_t; +/** @brief Serialized expression form */ +typedef struct rpn_expr_serial_s rpn_expr_serial_t; + /**@brief Stores RPN expression informations * @ingroup rpn */ @@ -134,6 +137,18 @@ struct rpn_expr_s char err_reason[128]; }; +/**@brief Serialized form of an RPN expression + * @ingroup rpn + */ +struct rpn_expr_serial_s +{ + size_t token_sz; + unsigned char stack_sz; + size_t argc; + short state; + char err_reason[128]; +}; + /**@brief Initialize a new @ref rpn_expr_s * @param expr Pointer on the expression * @param stack_sz Expression stack size @@ -148,10 +163,24 @@ int rpn_expr_init(rpn_expr_t* expr, const unsigned char stack_sz, /**@brief Reinit an existing @ref rpn_expr_s avoiding mmap and malloc * for executable memory map and for stack - * @param expr + * @param expr Pointer on the expression + * @return 0 or -1 on error */ int rpn_expr_reinit(rpn_expr_t* expr); +/**@brief Recompile an existing, allready initialized, expression + * @param expr The expression + * @param code The code to compile + * @return 0 if no error else -1 + */ +int rpn_expr_recompile(rpn_expr_t *expr, const char *code); + +/**@brief Takes into account modifications in token representation + * @param expr The expression with updated tokens + * @return 0 if no error else -1 + */ +int rpn_expr_tokens_updated(rpn_expr_t* expr); + /**@brief Starts a new initialized expression from an expression string * @param expr Pointer on an intialized expression ( see @ref rpn_expr_init ) * @param code '\0' terminated string representing the RPN expr @@ -166,14 +195,15 @@ int rpn_expr_compile(rpn_expr_t *expr, const char *code); * @param tokens Tokenized form * @param long_op If true long operation are used, else 1 chr op are used * @return 0 if no error else -1 and @ref rpn_expr_s::err_reason is set - * @note The @ref rpn_tokenized_s::tokens attribute is "captured" by the + * + * @note The @ref rpn_tokenized_s.tokens attribute is "captured" by the * @ref rpn_expr_s structure and will be deallocated when @ref rpn_expr_close * is called. It is not encouraged to keep a reference on this attribute after - * @ref rpn_expr_start_untokenize call (but pointed @ref rpn_tokenized_t + * @ref rpn_expr_untokenize call (but pointed @ref rpn_tokenized_t * can be reused) * @ingroup rpn */ -int rpn_expr_untokenize(rpn_expr_t *expr, rpn_tokenized_t *tokens, char long_op); +int rpn_expr_untokenize(rpn_expr_t *expr, const rpn_tokenized_t *tokens, char long_op); /**@brief Generate a new random rpn_expression * @param op_sz Number of token in generated expression @@ -190,7 +220,7 @@ char* rpn_random(size_t op_sz, size_t args_count); * @ingroup rpn_compile */ int _rpn_expr_compile_expr(rpn_expr_t* expr); -/**@brief Compile an new RPN expression from string expression +/**@brief Compile an new RPN expression from tokens * @param expr Pointer on @ref rpn_expr_s * @return 0 if no error else -1 and set @ref rpn_expr_s err_reason * @ingroup rpn_compile @@ -208,7 +238,6 @@ unsigned long rpn_expr_eval(rpn_expr_t *expr, unsigned long *args); /**@brief Free ressources handled by given @ref rpn_expr_s * @param expr Pointer on rpn_expr_t - * @param expr Pointer on @ref rpn_expr_s * @ingroup rpn */ void rpn_expr_close(rpn_expr_t* expr); @@ -219,6 +248,11 @@ void rpn_expr_close(rpn_expr_t* expr); */ void rpn_expr_reset_stack(rpn_expr_t *expr); +/**@todo document*/ +size_t rpn_expr_serialize(rpn_expr_t* expr, void *buf, size_t buf_sz); +/**@todo document*/ +int rpn_expr_deserialize(rpn_expr_t* expr, const void *buf, size_t buf_sz); + /**@brief Copy precompiled code from @ref rpn_lib.h in expression code map * @param expr The expression being compiled * @param token Pointer on token informations @@ -255,4 +289,10 @@ int _rpn_expr_init_map(rpn_expr_t* expr); */ int _rpn_expr_end_map(rpn_expr_t *expr); +/**@brief Reset the memory map, filling it with zeroes and reseting permissions + * @param expr Pointer on rpn_expr_t + * @return 0 if no error else -1 + */ +int _rpn_expr_reset_map(rpn_expr_t *expr); + #endif diff --git a/rpn_lib.asm b/rpn_lib.asm index 325c013..1155160 100644 --- a/rpn_lib.asm +++ b/rpn_lib.asm @@ -38,14 +38,18 @@ global %1_sz %endmacro +section .note.GNU-stack noalloc noexec nowrite progbits + section .data rpn_exec: - ; unsigned long int rpn_exec(unsigned long int stack_size, unsigned long args) + ; unsigned long int rpn_exec(unsigned long int stack_size, unsigned long args, unsigned long int *stack) ; rdi -> stack size (in values (64 bits)) ; rsi -> args pointers - + ; rdx -> stack pointer or null + enter 32, 0 + push rbx ; rbx MUST BE PRESERVED !!! (like r12 to r15 and rbp !) mov stack_size, rdi mov args_ptr, rsi @@ -100,6 +104,7 @@ rpn_exec_ret: syscall pop rax .end: + pop rbx leave ret part_sz rpn_exec_ret diff --git a/rpn_lib.h b/rpn_lib.h index f0594c1..7e097cb 100644 --- a/rpn_lib.h +++ b/rpn_lib.h @@ -50,7 +50,9 @@ #define CODE_SZ(NAME) NAME ## _sz /**@brief macro to declare a code part and associated size */ -#define CODE_PART(NAME) const extern void* NAME; extern const unsigned long NAME ## _sz +#define CODE_PART(NAME) const extern void* NAME; \ +/**@brief Cord part size */\ +extern const unsigned long NAME ## _sz /**@brief Define the type of value manipulated by RPN expressions * @@ -58,6 +60,11 @@ * @todo use it */ typedef unsigned long int rpn_value_t; +/**@brief Small alias for PyLong_FromUnsignedLong */ +#define PyLong_FromRpnValue_t PyLong_FromUnsignedLong +/**@brief Small alias for PyLong_AsUnsignedLong */ +#define PyLong_AsRpnValue_t PyLong_AsUnsignedLong + /**@brief Function heading code * * - stack frame creation diff --git a/rpn_mutate.c b/rpn_mutate.c new file mode 100644 index 0000000..0e2a449 --- /dev/null +++ b/rpn_mutate.c @@ -0,0 +1,402 @@ +#include "rpn_mutate.h" + +const rnd_t rnd_t_max = -1; + +rpn_mutation_params_t rpn_mutation_params_default = { + .min_len = 3, + .w_add = 1.25, + .w_del = 1.0, + .w_mut = 2.0, + .w_mut_soft = 4.0, + .w_add_elt = {1,1,1}, + .w_mut_elt={1,1,1}, +}; + +int rpn_mutation_init_params(rpn_mutation_params_t *params) +{ + float mut_weights[4] = {params->w_add, params->w_del, + params->w_mut, params->w_mut_soft}; + if(rpnifs_fast_rnd_weights(4, mut_weights, params->_weights)<0) + { + return -1; + } +/* +#ifdef DEBUG +dprintf(2, "summed weights : %d %d %d %d\n", + params->_weights[0], + params->_weights[1], + params->_weights[2], + params->_weights[3]); +#endif +*/ + /**@todo test this */ + if(rpnifs_fast_rnd_weights(3, params->w_add_elt, + &(params->_weights[4])) < 0) + { + return -1; + } + + if(rpnifs_fast_rnd_weights(3, params->w_mut_elt, + &(params->_weights[7])) < 0) + { + return -1; + } + + return 0; +} + +int rpn_mutation(rpn_tokenized_t *toks, rpn_mutation_params_t *params) +{ + //_print_params(2, params); + assert(toks->argc < rnd_t_max); + + if(toks->tokens_sz <= params->min_len) + { + return rpn_mutation_add(toks, params); + } + size_t choice; + if(_rpn_random_choice(4, params->_weights, &choice) < 0) + { + return -1; + } + switch(choice) + { + case 0: + return rpn_mutation_add(toks, params); + case 1: + return rpn_mutation_del(toks, params); + case 2: + return rpn_mutation_mut(toks, params); + case 3: + return rpn_mutation_mut_soft(toks, params); + default: + dprintf(2, "Random error that should never occurs"); + return -1; + } + +} +int rpn_mutation_add(rpn_tokenized_t *toks, rpn_mutation_params_t *params) +{ + rpn_token_t *new_toks, new_token; + size_t position; + + if(toks->tokens_sz == 0) + { + position = 0; + } + else if(rpn_rand_limit(toks->tokens_sz, &position) < 0) + { + return -1; + } + + if(rpn_random_token_type(&(new_token.type), ¶ms->_weights[4]) < 0) + { + return -1; + } + if(rpn_mutation_random_token(&new_token, toks, params) < 0) + { + return -1; + } + + toks->tokens_sz++; + new_toks = realloc(toks->tokens, sizeof(rpn_token_t) * toks->tokens_sz); + if(!new_toks) + { + toks->tokens_sz--; + return -1; + } + toks->tokens = new_toks; + + if(position < toks->tokens_sz - 1) + { + memmove(&(toks->tokens[position+1]), &(toks->tokens[position]), + sizeof(rpn_token_t) * ((toks->tokens_sz-1) - position)); + } + memcpy(&toks->tokens[position], &new_token, sizeof(rpn_token_t)); + + return 0; +} +int rpn_mutation_del(rpn_tokenized_t *toks, rpn_mutation_params_t *params) +{ + rpn_token_t *new_toks; + size_t position; + + if(rpn_rand_limit(toks->tokens_sz-1, &position) < 0) + { + return -1; + } + + if(position != toks->tokens_sz - 1) + { + memmove(&(toks->tokens[position]), &(toks->tokens[position+1]), + sizeof(rpn_token_t) * ((toks->tokens_sz - 1) - position)); + } + toks->tokens_sz--; + new_toks = realloc(toks->tokens, sizeof(rpn_token_t) * toks->tokens_sz); + if(!new_toks) + { + toks->tokens_sz = 0; + toks->tokens = NULL; + return -1; + } + toks->tokens = new_toks; + return 0; +} +int rpn_mutation_mut(rpn_tokenized_t *toks, rpn_mutation_params_t *params) +{ + rpn_token_t *tok; + size_t position; + + if(rpn_rand_limit(toks->tokens_sz-1, &position) < 0) + { + return -1; + } + + tok = &toks->tokens[position]; + + if(rpn_random_token_type(&(tok->type), &(params->_weights[7])) < 0) + { + return -1; + } + if(rpn_mutation_random_token(tok, toks, params) < 0) + { + return -1; + } + + return 0; +} +int rpn_mutation_mut_soft(rpn_tokenized_t *toks, rpn_mutation_params_t *params) +{ + rpn_token_t *tok; + size_t position; + + if(rpn_rand_limit(toks->tokens_sz-1, &position) < 0) + { + return -1; + } + + tok = &(toks->tokens[position]); + + if(rpn_mutation_random_token(tok, toks, params) < 0) + { + return -1; + } + + return 0; +} + + +int rpn_mutation_random_token(rpn_token_t *tok, + rpn_tokenized_t *toks, rpn_mutation_params_t *params) +{ + rnd_t rand; + + if(rpn_getrandom(&rand) < 0) + { + return -1; + } + + unsigned char op_n; + unsigned char *val; + size_t left; + switch(tok->type) + { + case RPN_op: + op_n = rand / (rnd_t_max/RPN_OP_SZ); + op_n %= RPN_OP_SZ; + tok->op_n = op_n; + tok->op = &(rpn_ops[op_n]); + break; + + case RPN_arg: + tok->arg_n = rand / (rnd_t_max/toks->argc); + tok->arg_n %= toks->argc; + break; + case RPN_val: + val =(unsigned char*)&(tok->value); + left = sizeof(tok->value); + do + { + ssize_t ret = getrandom(val, left, GRND_NONBLOCK); + if(ret == -1) + { + if(errno == EAGAIN || errno == EINTR) + { + continue; + } + return -1; + } + left -= ret; + val += ret; + }while(left); + break; + default: + dprintf(2, "Another random error that shouldn't occur\n"); + return -1; + } + return 0; +} + + +int rpn_random_token_type(rpn_token_type_t *type, rnd_t *weights) +{ + const rpn_token_type_t types[3] = { RPN_op, RPN_arg, RPN_val }; + size_t choice; + + if(_rpn_random_choice(3, weights, &choice) < 0) + { + return -1; + } + *type = types[choice]; + return 0; +} + +int rpn_getrandom(rnd_t *rand) +{ + rnd_t seed; + char *seed_ptr = (char*)&seed; + size_t buflen = sizeof(seed); + + do + { + ssize_t ret = getrandom(seed_ptr, buflen, GRND_NONBLOCK); + if(ret == -1) + { + if(errno == EAGAIN || errno == EINTR) + { + continue; + } + return -1; + } + buflen -= ret; + seed_ptr += ret; + }while(buflen); + *rand = seed; + return 0; +} + +int rpnifs_fast_rnd_weights(size_t sz, const float *weights, + rnd_t *fast_weights) +{ + long double total = 0; + for(size_t i=0; i0) + { + w += fast_weights[i-1]; + } + } + fast_weights[i] = w; + } + return 0; +} + +int rpnifs_rnd_float(float range[2], float *res) +{ +#if DEBUG + if(range[0] >= range[1]) + { + errno = EINVAL; + return -1; + } +#endif + const rnd_t mid = rnd_t_max / 2; + rnd_t rand; + short neg; + + if(rpn_getrandom(&rand) < 0) { return -1; } + if(rand >= mid) + { + neg = 1; + rand -= mid; + } + else + { + neg = 0; + } + *res = ((rand*(range[1]-range[0]))/mid) + range[0]; + *res *= neg?-1:1; + return 0; +} + +int _rpn_random_choice(size_t sz, rnd_t *weights, size_t *res) +{ + if(weights) + { + rnd_t rand; + if(rpn_getrandom(&rand) < 0) { return -1; } + __rpn_random_choice(sz, weights, rand, res); + return 0; + } + // unweighted choice + rnd_t rand; + unsigned long long div = rnd_t_max; + div++; + div /= sz; + if(rpn_getrandom(&rand) < 0) { return -1; } + *res = (rnd_t)(((unsigned long long)rand)/div); + return 0; +} + +void __rpn_random_choice(size_t sz, rnd_t *weights, rnd_t rand, size_t *res) +{ + for(*res=0; *res= rand) + { + return; + } + } + *res = sz-1; +} + +int rpn_rand_limit(size_t max, size_t *res) +{ + unsigned long int step = rnd_t_max / max; + rnd_t rnd; + + if(rpn_getrandom(&rnd) < 0) + { + return -1; + } + for(*res=0; *res= rnd) + { + return 0; + } + } + //*res = max - 1; + return 0; +} + +void _print_params(int fd, rpn_mutation_params_t *params) +{ + dprintf(fd, "Minlen = %ld w[add=%.2f del=%.2f mut=%.2f msf=%.2f]\n", + params->min_len, params->w_add, params->w_del, + params->w_mut, params->w_mut_soft); + dprintf(fd, "w_add_elt [op=%.2f const=%.2f var=%.2f]\n", + params->w_add_elt[0], params->w_add_elt[1], + params->w_add_elt[2]); + dprintf(fd, "w_mut_elt [op=%.2f const=%.2f var=%.2f]\n", + params->w_mut_elt[0], params->w_mut_elt[1], + params->w_mut_elt[2]); +} + diff --git a/rpn_mutate.h b/rpn_mutate.h new file mode 100644 index 0000000..9d44dcb --- /dev/null +++ b/rpn_mutate.h @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2020,2023 Weber Yann + * + * This file is part of pyrpn. + * + * pyrpn is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * pyrpn is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with pyrpn. If not, see . + */ +#ifndef __rpn_mutate__h__ +#define __rpn_mutate__h__ +#include "config.h" + +#include +#include +#include +#include + +#include "rpn_parse.h" + +/**@file rpn_mutate.h + * @brief Expression mutation headers + */ + +/**@brief RPN expression mutation parameters */ +typedef struct rpn_mutation_params_s rpn_mutation_params_t; +/**@brief Type of random values used by mutations */ +typedef uint16_t rnd_t; + +/**@brief maximum value for rnd_t */ +extern const rnd_t rnd_t_max; + +/**@brief Default values for mutations parameters */ +extern rpn_mutation_params_t rpn_mutation_params_default; + +/**@brief RPN expression mutation parameters */ +struct rpn_mutation_params_s +{ + /**@brief Minimum expression length */ + size_t min_len; + /**@brief Weight of adding a token*/ + float w_add; + /**@brief Weight of deleting a token*/ + float w_del; + /**@brief Weight of mutating a token*/ + float w_mut; + /**@brief Weight of mutating a token without type change*/ + float w_mut_soft; + /**@brief Weight for each token types (op, const, var) when + * adding a token*/ + float w_add_elt[3]; + /**@brief Weight for each token types (op, const, var) when + * mutating a token*/ + float w_mut_elt[3]; + + /**@brief For internal use, set by rpn_mutation_init_params + * + * weight reported by groups on rnd_t integers + * - [0..3] -> weights mutation + * - [4..6] -> w_add_elt + * - [7..9] -> w_mut_elt + * + * @note Weights are stored a way allowing fast random choice. + * They are kind of ascending threshold until @ref rnd_t max value. + */ + rnd_t _weights[10]; +}; + + +/**@brief Initialize mutation parameters + * + * pre-process integers threshold by group + * @param params + * @return 0 if no error else -1 and set ERRNO (EINVAL) + */ +int rpn_mutation_init_params(rpn_mutation_params_t *params); + +/**@brief Mutate a tokenized rpn expression + * @param toks The tokenized expression + * @param params The mutation parameters + * @return 0 or -1 on error */ +int rpn_mutation(rpn_tokenized_t *toks, rpn_mutation_params_t *params); + +/**@brief Mutate an expression by adding a token + * @param toks The tokenized expression + * @param params The mutation parameters + * @return 0 or -1 on error */ +int rpn_mutation_add(rpn_tokenized_t *toks, rpn_mutation_params_t *params); +/**@brief Mutate an expression by removing a token + * @param toks The tokenized expression + * @param params The mutation parameters + * @return 0 or -1 on error */ +int rpn_mutation_del(rpn_tokenized_t *toks, rpn_mutation_params_t *params); +/**@brief Mutate an expression by changing a token + * @param toks The tokenized expression + * @param params The mutation parameters + * @return 0 or -1 on error */ +int rpn_mutation_mut(rpn_tokenized_t *toks, rpn_mutation_params_t *params); +/**@brief Mutate an expression by changing a token value (not type) + * @param toks The tokenized expression + * @param params The mutation parameters + * @return 0 or -1 on error */ +int rpn_mutation_mut_soft(rpn_tokenized_t *toks, rpn_mutation_params_t *params); + +/**@brief Change the "value" of a token randomly not it's type + * @param tok Point on the token to mutate + * @param toks The tokenized expression the token tok belongs to + * @param params The mutation parameters + * @return 0 or -1 on error */ +int rpn_mutation_random_token(rpn_token_t *tok, + rpn_tokenized_t *toks, rpn_mutation_params_t *params); + + +/**@brief Choose a random token type + * @param type Point on result + * @param weights The weight of each types for the choice + * @note Weights comes from @ref rpn_mutation_params_s._weights and are + * processed in a form allowing a fast random choice. + * @return 0 or -1 on error + */ +int rpn_random_token_type(rpn_token_type_t *type, rnd_t *weights); + +/**@brief Generate a random value + * @param rand Point on result + * @return -1 on error else 0 */ +int rpn_getrandom(rnd_t *rand); + +/**@brief Generate a random number between 0 and max (max excluded) + * @param max Maximum value + * @param res Will be set to a value between [..max[ + * @return -1 on error else 0 */ +int rpn_rand_limit(size_t max, size_t *res); + +/**@brief Conver an array of float weights to an array for fast random + * choices (see @ref _rpn_random_choices() ) + * @param sz The number of items in to choose from + * @param weights Array of size sz with items weights (positive values) + * @param fast_weights Array of size sz pointing on result + * @return -1 on error else 0 */ +int rpnifs_fast_rnd_weights(size_t sz, const float *weights, + rnd_t *fast_weights); + +/** + * @return A float between [-range[1] .. -range[0]] or [range[0] .. range[1]] + */ +int rpnifs_rnd_float(float range[2], float *res); + +/**@brief Given a size return an random element with regards to given weights + * @param sz The given size + * @param weights Weights coming from @ref rpn_mutation_params_s._weights + * @param res Points on result + * @return 0 or -1 on error + * @todo refactor rename without rpn prefix */ +int _rpn_random_choice(size_t sz, rnd_t *weights, size_t *res); + +/**@brief Given a size and a (random) value, returns an element with regards + * to given weights + * @param sz The given size + * @param weights Weights coming from @ref rpn_mutation_params_s._weights + * @param rand A (random) value + * @param res Points on result */ +void __rpn_random_choice(size_t sz, rnd_t *weights, rnd_t rand, size_t *res); + +/**@brief Debugging function that dump mutation params in a human readable format + * @param fd The file descriptor on wich we should print parameters + * @param params The mutation parameters to dump */ +void _print_params(int fd, rpn_mutation_params_t *params); + +#endif diff --git a/rpn_mutation.c b/rpn_mutation.c deleted file mode 100644 index cf38fb9..0000000 --- a/rpn_mutation.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2020 Weber Yann - * - * This file is part of pyrpn. - * - * pyrpn is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * pyrpn is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with pyrpn. If not, see . - */ -#include "rpn_mutation.h" - -/**@file rpn_mutation.c - * @todo continue implementation */ - -const rpn_mutation_profile_t rpn_default_mutprof = {16, - { RPN_del, RPN_del, - RPN_add, RPN_add, - RPN_chg, RPN_chg, RPN_chg, RPN_chg, RPN_chg, RPN_chg, - RPN_upd, RPN_upd, RPN_upd, RPN_upd, RPN_upd, RPN_upd} -}; - -rpn_expr_t* rpn_expr_mutation(rpn_expr_t *src, size_t mutations) -{ - return rpn_expr_mutation_p(src, mutations, &rpn_default_mutprof); -} - -rpn_expr_t* rpn_expr_mutation_p(rpn_expr_t *src, size_t mutations, - const rpn_mutation_profile_t *prof) -{ - unsigned char op; - - prof = prof?prof:&rpn_default_mutprof; - - op = prof->mods[(int)(drand48() / (1.0 / prof->mods_sz))]; - switch(op) - { - case 0: // add a token - break; - case 1: // delete a token - break; - case 2: // update token type - break; - default: // update token, same type - break; - } - return NULL; -} - - diff --git a/rpn_mutation.h b/rpn_mutation.h deleted file mode 100644 index ba99521..0000000 --- a/rpn_mutation.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2020 Weber Yann - * - * This file is part of pyrpn. - * - * pyrpn is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * pyrpn is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with pyrpn. If not, see . - */ -#ifndef __rpn_mutation__h__ -#define __rpn_mutation__h__ - -#include - -#include "rpn_jit.h" - -/**@defgroup mutation RPN expression mutation - * @ingroup rpn - */ -/**@file rpn_mutation.h - * @brief Contains structures and function to mutate RPN epxressions - */ - - -/**@brief Defines mutation actions types */ -enum rpn_mutation_op_e { - /**@brief Mutation action : delete a token */ - RPN_del, - /**@brief Mutation action : add a token */ - RPN_add, - /**@brief Mutation action : change a token */ - RPN_chg, - /**@brief Mutation action : update a token (same type, different value) */ - RPN_upd -}; - -/**@brief Shortcut for struct @ref rpn_mutation_profile_s */ -typedef struct rpn_mutation_profile_s rpn_mutation_profile_t; - -/**@brief Stores mutation informations - * @ingroup mutation - */ -struct rpn_mutation_profile_s -{ - /**@brief Size of @ref rpn_mutation_profile_s::mods attribute */ - size_t mods_sz; - - /**@brief Modification possibilities - * - * One value is picked up randomly from this list to determine - * the type of mutation : addition, deletion, modification, value change - */ - unsigned char mods[]; -}; - -/**@brief Default mutation profile */ -extern const rpn_mutation_profile_t rpn_default_mutprof; - -/**@brief Shortcut for @ref rpn_expr_mutation_p with a @ref rpn_default_mutprof - * @ingroup mutation */ -rpn_expr_t* rpn_expr_mutation(rpn_expr_t *src, size_t mutations); - -/**@brief Generate a new expression by applying mutations to a source - * expression - * @param src Source expression - * @param mutations number of mutations - * @param prof Mutation profile - * @return A new instance of rpn_expr_t ready to be evaluate - * @ingroup mutation - */ -rpn_expr_t* rpn_expr_mutation_p(rpn_expr_t *src, size_t mutations, - const rpn_mutation_profile_t *prof); - -#endif diff --git a/rpn_parse.c b/rpn_parse.c index 0bbd76a..ff987d9 100644 --- a/rpn_parse.c +++ b/rpn_parse.c @@ -42,6 +42,7 @@ const rpn_op_t rpn_ops[] = {\ __op(rpn_pop_op, 'p', "pop"),\ }; #undef __op +const size_t RPN_OP_SZ = (sizeof(rpn_ops) / sizeof(rpn_op_t)); int rpn_tokenizer_start(rpn_tokenizer_t *tokenizer, rpn_tokenized_t *dst, const char* expr, size_t argc) @@ -213,7 +214,7 @@ void rpn_tokenizer_free(rpn_tokenizer_t *tokenizer) } } -char* rpn_tokenized_expr(rpn_tokenized_t *tokens, char long_op) +char* rpn_tokenized_expr(const rpn_tokenized_t *tokens, char long_op) { size_t expr_sz, i; int err, written; @@ -244,7 +245,7 @@ char* rpn_tokenized_expr(rpn_tokenized_t *tokens, char long_op) for(i=0; itokens_sz; i++) { token = &(tokens->tokens[i]); - if(cur - expr >= expr_sz - ALLOC_CHUNK) + if((size_t)(cur - expr) >= (expr_sz - ALLOC_CHUNK)) { expr_sz += 128; tmp = realloc(expr, sizeof(char) * expr_sz); @@ -253,7 +254,7 @@ char* rpn_tokenized_expr(rpn_tokenized_t *tokens, char long_op) err=errno; dprintf(2,"Error allocating memory for expression : %s", strerror(err)); - goto ret_err; + goto free_err; } cur = tmp + (cur - expr); expr = tmp; @@ -303,7 +304,11 @@ char* rpn_tokenized_expr(rpn_tokenized_t *tokens, char long_op) #endif cur += written; } - + + if(cur > expr) + { + *(cur-1) = '\0'; + } return expr; free_err: @@ -323,6 +328,15 @@ const rpn_op_t* rpn_match_token(const char* token) return &(rpn_ops[rep]); } +const rpn_op_t* rpn_op_from_opcode(unsigned char opcode) +{ + if(opcode > RPN_OP_SZ) + { + return NULL; + } + return &(rpn_ops[opcode]); +} + int rpn_match_token_i(const char* token) { unsigned char i; @@ -386,6 +400,28 @@ int rpn_match_number(const char* token, unsigned long *result) return 0; } +int rpn_token_snprintf(rpn_token_t *tok, char *dst, size_t sz) +{ + switch(tok->type) + { + case RPN_op: + if(tok->op->chr) + { + return snprintf(dst, sz, "%c", tok->op->chr); + } + return snprintf(dst, sz, "%s", tok->op->str); + case RPN_val: + return snprintf(dst, sz, "0x%lX", tok->value); + case RPN_arg: + return snprintf(dst, sz, "A%lu", tok->arg_n); + default: + errno = EINVAL; + return -1; + } + +} + + size_t rpn_op_sz() { return sizeof(rpn_ops)/sizeof(rpn_op_t); diff --git a/rpn_parse.h b/rpn_parse.h index 976af85..2d1088d 100644 --- a/rpn_parse.h +++ b/rpn_parse.h @@ -37,7 +37,7 @@ * @brief Parsing an expression into a @ref rpn_tokenized_t * * The tokenized form ( see @ref rpn_tokenized_t ) of an expression is usefull - * for @ref mutation. + * for mutations (see @ref rpn_mutate.h ). * * The tokenizing process is done in a way allowing compilation process to * fetch tokens while parsing the expression (see @ref rpn_tok). @@ -49,7 +49,7 @@ */ /**@brief Shortcut for loop on all operations list */ -#define foreach_rpn_ops(IDX) for(IDX=0; IDX 1: - expr_count = int(sys.argv[1], 0) -if len(sys.argv) > 2: - max_iter = int(sys.argv[2], 0) -if len(sys.argv) > 3: - sz = int(sys.argv[4], 0) -if len(sys.argv) > 4: - argc = int(sys.argv[3], 0) tot = 0 time_op = 0 @@ -48,20 +55,27 @@ start = time.time() IMAX = (1<<63)-1 samples = 8 -print("Running %dK iter on %d expressions with %d op" % (max_iter//1000, +print("========\nIF :", end=" ") +print("Running %dK iter on %d expressions with %d op and %d args" % (max_iter//1000, expr_count, - sz)) + sz, argc)) +res = [0 for _ in range(512)] rnd_samples = [[[random.randint(0, IMAX) for _ in range(argc)] for _ in range(max_iter)] for _ in range(samples)] + for i in tqr(expr_count): rnd_expr = pyrpn.random_expr(argc, sz) all_start = time.time() expr = pyrpn.RPNExpr(rnd_expr, argc) + expr.eval(*[0 for _ in range(argc)]) start_op = time.time() rnds = random.choice(rnd_samples) for rnd in rnds: - expr.eval(*rnd) + r = expr.eval(*rnd) + # emulate memory access for simple if to make comparison + # consistant with IFS + res[r%512] += 1 time_op += time.time() - start_op tot += time.time() - all_start if write: @@ -70,9 +84,59 @@ for i in tqr(expr_count): if write: sys.stderr.write("\n") total = time.time() - start -print("Runned %dK iter on %d expressions in %.2fs" % (max_iter//1000, - expr_count, total)) -msg = "%.1fK iter/s %.1fms per expr (%dK iter, %d op per expr)" -msg %= (max_iter*expr_count/time_op/1000, tot/expr_count*1000, + +ops = max_iter*expr_count*sz/time_op/(1000*1000) +msg = "%5.1fM op/s %6.1fK iter/s %.1fms per expr (%dK iter, %d op per expr)" +msg %= (ops, + max_iter*expr_count/time_op/1000, + tot/expr_count*1000, max_iter//1000, sz) print(msg) +print("++++++++") +if_ops = ops + + +if_sz = 5 +ifs_count = expr_count // if_sz +ifs_sz = 3 +mem_sz = (512,512) +expr_count = ifs_count * if_sz + +print("IFS :", end=" ") +print("Running %dK iter on %d ifs (sz=%d with %d if choosen randomly and %d op) RGB of size %r" % \ + (max_iter/1000, ifs_count , if_sz, ifs_sz, sz, mem_sz)) + +ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY, pyrpn.const.RESULT_RGB, mem_sz) +runtime = 0 +all_start = time.time() +for i in tqr(ifs_count): + weights = [random.randint(1,50) for _ in range(ifs_sz)] + ifs.weights(weights) + for i in range(ifs_sz): + for expr_id in 'RGBXY': + ifs[i][expr_id] = pyrpn.random_expr(if_sz, sz) + + rnd = os.getrandom(max_iter) + start = time.time() + ifs.run(max_iter, rnd) + runtime += time.time() - start + if write: + sys.stderr.write(".") + sys.stderr.flush() +if write: + sys.stderr.write("\n") + +total = time.time() - all_start +ops = max_iter*ifs_count*if_sz*sz/(runtime*1000*1000) +msg = "%5.1fM op/s %6.1fK expr_iter/s %.2fms per ifs" +msg %= (ops, + max_iter*ifs_count*if_sz/runtime/1000, + total/ifs_count) +ifs_ops = ops +print(msg) +print("========") + +if gpout: + line = "%d %d %f %f\n" % (max_iter, sz, if_ops, ifs_ops) + gpout.write(line) + gpout.close() diff --git a/tests/pyrpn.so b/tests/pyrpn.so deleted file mode 120000 index 16386cd..0000000 --- a/tests/pyrpn.so +++ /dev/null @@ -1 +0,0 @@ -../pyrpn.so \ No newline at end of file diff --git a/tests/test_rpn.c b/tests/test_rpn.c index e87a439..47a84f2 100644 --- a/tests/test_rpn.c +++ b/tests/test_rpn.c @@ -28,6 +28,8 @@ #include "rpn_lib.h" #include "rpn_jit.h" #include "rpn_parse.h" +#include "rpn_if.h" +#include "rpn_if_default.h" int test0() { @@ -111,6 +113,18 @@ int test_add() res); return 2; } + + // test token update + expr.toks.tokens[0].value = 15; + rpn_expr_tokens_updated(&expr); + res = rpn_expr_eval(&expr, NULL); + //printf("Result = %ld\n", res); + if(res != 41) + { + dprintf(2, "Error : expected 42 but %ld received\n", + res); + return 3; + } rpn_expr_close(&expr); return 0; } @@ -227,6 +241,59 @@ int test_tokenization() return 0; } +int test_rpn_if_default() +{ + rpn_if_param_t *rif_param; + rpn_if_t *rif; + size_t lim[2] = {1024,768}; + + rif_param = rpn_if_default_params(RPN_IF_POSITION_XY, RPN_IF_RES_RGB, + lim, NULL, 32); + if(!rif_param) + { + fprintf(stderr, "rpn_if_default_params() failed\n"); + return -1; + } + + rif = rpn_if_new(rif_param, NULL, NULL); + if(!rif) + { + perror("rpn_if_new() failed"); + return -1; + } + + rpn_if_free(rif); + free(rif_param); + + return 0; +} + +int test_rpn_if_default2() +{ + rpn_if_param_t *rif_param; + rpn_if_t *rif; + size_t lim[2] = {1024}; + + rif_param = rpn_if_default_params(RPN_IF_POSITION_LINEAR, RPN_IF_RES_RGB, + lim, NULL, 32); + if(!rif_param) + { + fprintf(stderr, "rpn_if_default_params() failed\n"); + return -1; + } + + rif = rpn_if_new(rif_param, NULL, NULL); + if(!rif) + { + perror("rpn_if_new() failed"); + return -1; + } + + rpn_if_free(rif); + free(rif_param); + + return 0; +} @@ -254,5 +321,7 @@ int main() RUNTEST(test_args); RUNTEST(test_stack_sz); RUNTEST(test_tokenization); + RUNTEST(test_rpn_if_default); + RUNTEST(test_rpn_if_default2); return res; } diff --git a/tests/tests_pyrpn.py b/tests/tests_pyrpn.py index c044f85..973c322 100755 --- a/tests/tests_pyrpn.py +++ b/tests/tests_pyrpn.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 -# Copyright 2020 Weber Yann +# Copyright 2020, 2023 Weber Yann # -# This file is part of geneifs. +# This file is part of rpnifs. # # geneifs is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,6 +17,7 @@ # along with geneifs. If not, see . # +import copy import sys import random import math @@ -34,6 +35,7 @@ except (ImportError, NameError) as e: file=sys.stderr) raise e + class Test0RpnModule(unittest.TestCase): def test_init(self): @@ -42,10 +44,11 @@ class Test0RpnModule(unittest.TestCase): def test_init_badargs(self): """ RPNExpr instanciation with bad arguments """ - badargs = [('', 2), (), ('ab+',), ('ab+',300), (42, 42), ('ab', '42')] + badargs = [(), ('ab+',), ('ab+',300), (42, 42), ('ab', '42')] for badarg in badargs: - with self.assertRaises((ValueError, TypeError)): - expr = pyrpn.RPNExpr(*badarg) + with self.subTest(badargs=badarg): + with self.assertRaises((ValueError, TypeError)): + expr = pyrpn.RPNExpr(*badarg) def test_init_loop(self): """ Testing pyrpn.RPNExpr multiple instanciation """ @@ -89,6 +92,15 @@ class Test0RpnModule(unittest.TestCase): self.assertIn(long, known_ops) self.assertEqual(short, known_ops[long]) + def testing_rand_expr_len(self): + """ Testing if RPNExpr.random_expr() returns expected expression """ + for _ in range(100): + elen = random.randint(1, 50) + rnd_expr = pyrpn.random_expr(20, elen) + spl = [s for s in rnd_expr.split(' ') if len(s.strip())] + with self.subTest(expected_tokens=elen, tokens_count=len(spl), expr=rnd_expr): + self.assertEqual(len(spl), elen) + def test_rand_expr(self): """ Testing RPNExpr.random_expr() """ result = {} @@ -114,11 +126,11 @@ class Test0RpnModule(unittest.TestCase): if tok not in counters: counters[tok] = 0 counters[tok] += 1 - all_ops = len(pyrpn.get_ops()) + 2 + all_ops = len(pyrpn.get_ops()) + 1 entropy = 1-sum([(n/all_count)**2 for _, n in counters.items()]) self.assertGreater(entropy, 1-(1/all_ops), "Low entropy !") - + if __name__ == '__main__': unittest.main() diff --git a/tests/tests_rpn_compile.py b/tests/tests_rpn_compile.py index f0cdd14..c6468d7 100755 --- a/tests/tests_rpn_compile.py +++ b/tests/tests_rpn_compile.py @@ -34,6 +34,8 @@ except (ImportError, NameError) as e: file=sys.stderr) raise e +from utils import Progress, VERBOSE + class TestRpnCompile(unittest.TestCase): def test_basic(self): @@ -55,11 +57,9 @@ class TestRpnCompile(unittest.TestCase): def test_long_code(self): """ Compile longs expressions (from 256 to 65536 op )""" - for i in range(0x100,0x10000,0x500): + for i in Progress(range(0x100,0x10000,0x500)): with self.subTest('Testing expression with %X ops' % i): for argc in range(1,32, 8): - sys.stderr.write('.') - sys.stderr.flush() args = [random.randint(0,IMAX) for _ in range(argc)] expr = pyrpn.RPNExpr(pyrpn.random_expr(argc, i), argc) del(expr) @@ -67,18 +67,21 @@ class TestRpnCompile(unittest.TestCase): def test_very_long(self): """ Compile 3 very long expression (5242K op) """ argc = 4 - codelen = 0x500000 + codelen = 0x50000 import time - for i in range(3): + for i in Progress(3): args = [random.randint(0,IMAX) for _ in range(argc)] - sys.stderr.write('Generate') - sys.stderr.flush() + if VERBOSE: + sys.stderr.write('Generate') + sys.stderr.flush() expr_str = pyrpn.random_expr(argc, codelen) - sys.stderr.write('d Compile') - sys.stderr.flush() + if VERBOSE: + sys.stderr.write('d Compile') + sys.stderr.flush() expr = pyrpn.RPNExpr(expr_str, argc) - sys.stderr.write('d ') - sys.stderr.flush() + if VERBOSE: + sys.stderr.write('d ') + sys.stderr.flush() del(expr) def test_pickling(self): @@ -89,8 +92,10 @@ class TestRpnCompile(unittest.TestCase): pik = pickle.dumps(expr) new_expr = pickle.loads(pik) self.assertEqual(str(expr), str(new_expr)) + pik2 = pickle.dumps(new_expr) args = [random.randint(0,IMAX) for _ in range(argc)] self.assertEqual(expr.eval(*args), new_expr.eval(*args)) + self.assertEqual(pik, pik2) if __name__ == '__main__': diff --git a/tests/tests_rpn_copy.py b/tests/tests_rpn_copy.py new file mode 100755 index 0000000..56a20b0 --- /dev/null +++ b/tests/tests_rpn_copy.py @@ -0,0 +1,110 @@ +#!/usr/bin/python3 +# Copyright 2023 Weber Yann +# +# This file is part of rpnifs. +# +# geneifs is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# geneifs is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with geneifs. If not, see . +# + +import copy +import pickle +import sys + +import unittest + + +try: + import pyrpn +except (ImportError, NameError) as e: + print("Error importing pyrpn. Try to run make.", + file=sys.stderr) + raise e + + +## TODO : move this function somewhere or implement it in C +def decode_state(state): + """ Return a dict containing rpn state dump fields + """ + res = dict() + res['real_sz'] = len(state) + res['total_sz'] = int.from_bytes(state[0:8], byteorder="little") + res['argc'] = int.from_bytes(state[8:16], byteorder="little") + res['stack_sz_bytes'] = state[16:24] + res['stack_sz'] = state[16] + res['token_sz'] = int.from_bytes(state[24:32], byteorder="little", signed=True) + res['stack'] = [int.from_bytes(state[i:i+8], byteorder="little") + for i in range(32, 32 + (8*res['stack_sz']),8)] + res['tokens_off'] = 32 + (8*res['stack_sz']) + res['tokens_real_sz'] = res['real_sz'] - res['tokens_off'] + res['tokens'] = [state[i:i+24] + for i in range(res['tokens_off'], + res['tokens_off'] + (24*res['token_sz']), + 24)] + return res + + + +class TestRpnExprState(unittest.TestCase): + """ Testing RPNExpr "state" related methods (__copy__, __get/setstate__) + """ + + def test_pickle_state(self): + """ Testing pickling/unpickling """ + e = pyrpn.RPNExpr('0x42 + A0', 2) + e2 = pickle.loads(pickle.dumps(e)) + ds_e = decode_state(e.__getstate__()) + ds_e2 = decode_state(e2.__getstate__()) + self.assertEqual(e.__getstate__(), e2.__getstate__(), + msg="EXPR: %r != %r (%r != %r)" % (e, e2, ds_e, ds_e2)) + for i in range(100): + e = pyrpn.RPNExpr(pyrpn.random_expr(0), 2) + e.mutate(n_mutations=15); + + e2 = pickle.loads(pickle.dumps(e)) + + self.assertEqual(e.__getstate__(), e2.__getstate__(), + msg="EXPR#%d : %r != %r" % (i,e, e2)) + + e3 = pickle.loads(pickle.dumps(e2)) + + self.assertEqual(e.__getstate__(), e3.__getstate__(), msg=e) + self.assertEqual(e2.__getstate__(), e3.__getstate__(), msg=e) + + + + def test_copy(self): + """ Basic copy test based on pickling """ + e = pyrpn.RPNExpr('0x42 + A0', 2) + e2 = copy.copy(e) + ds_e = decode_state(e.__getstate__()) + ds_e2 = decode_state(e2.__getstate__()) + self.assertEqual(e.__getstate__(), e2.__getstate__(), + msg="EXPR: %r != %r (%r != %r)" % (e, e2, ds_e, ds_e2)) + for i in range(100): + e = pyrpn.RPNExpr(pyrpn.random_expr(0), 2) + + e2 = copy.copy(e) + + self.assertEqual(e.__getstate__(), e2.__getstate__(), + msg="EXPR#%d : %r != %r" % (i,e, e2)) + + e3 = pickle.loads(pickle.dumps(e2)) + + self.assertEqual(e.__getstate__(), e3.__getstate__(), msg=e) + self.assertEqual(e2.__getstate__(), e3.__getstate__(), msg=e) + + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/tests_rpn_eval.py b/tests/tests_rpn_eval.py index 3a081b8..bd5cefd 100755 --- a/tests/tests_rpn_eval.py +++ b/tests/tests_rpn_eval.py @@ -34,6 +34,8 @@ except (ImportError, NameError) as e: file=sys.stderr) raise e +from utils import Progress + class TestRpnEval(unittest.TestCase): def test_arithm(self): @@ -50,10 +52,8 @@ class TestRpnEval(unittest.TestCase): #('l', '<<'), #('r', '>>') ] - for rpn_op, pyop in ops: + for rpn_op, pyop in Progress(ops): with self.subTest('Testing op %s (%s)' % (rpn_op, pyop)): - sys.stderr.write('.') - sys.stderr.flush() for i in range(0x1000): op1, op2 = random.randint(0,IMAX), random.randint(1,IMAX) pyexpr = '%d %s %d' % (op1, pyop, op2) @@ -117,7 +117,7 @@ class TestRpnEval(unittest.TestCase): self.assertEqual(res, 0) - def test_airthm_extended(self): + def test_arithm_extended(self): """ Extended arithmetic tests """ exprs = ( ('A0 A1 +', '{0} + {1}', 2), diff --git a/tests/tests_rpn_ifs.py b/tests/tests_rpn_ifs.py new file mode 100755 index 0000000..d244bc1 --- /dev/null +++ b/tests/tests_rpn_ifs.py @@ -0,0 +1,361 @@ +#!/usr/bin/python3 +# copyright 2023 weber yann +# +# this file is part of rpnifs. +# +# geneifs is free software: you can redistribute it and/or modify +# it under the terms of the gnu general public license as published by +# the free software foundation, either version 3 of the license, or +# (at your option) any later version. +# +# geneifs is distributed in the hope that it will be useful, +# but without any warranty; without even the implied warranty of +# merchantability or fitness for a particular purpose. see the +# gnu general public license for more details. +# +# you should have received a copy of the gnu general public license +# along with geneifs. if not, see . +# + +import copy +import mmap +import pickle +import random +import sys + +import unittest + +try: + import numpy as np +except (ImportError, NameError) as e: + np = None + warnings.warn("Numpy not found, some tests skipped", warnings.ImportWarning) + +def skipIfNoNumpy(): + if np is not None: + return lambda func: func + return unittest.skip("Numpy not found") + +try: + import pyrpn +except (ImportError, NameError) as e: + print("error importing pyrpn. try to run make.", + file=sys.stderr) + raise e + +from utils import * + +class TestRPNIFS(unittest.TestCase): + """ Testing RPNIFS features """ + + def test_init(self): + ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY, + pyrpn.const.RESULT_COUNT, + (640,480)) + self.assertEqual(len(ifs), 0) + + def test_weights_set(self): + ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY, + pyrpn.const.RESULT_COUNT, + (640,480)) + self.assertEqual(len(ifs), 0) + w = ifs.weights([1,2,4]) + self.assertEqual(len(ifs), 3) + self.assertEqual(w, (1,2,4)) + + w = ifs.weights() + self.assertEqual(w, (1,2,4)) + + for ifs_len in [random.randint(1,10) for _ in range(10)]: + for _ in range(10): + w = [random.randint(1,1000) for _ in range(ifs_len)] + ifs.weights(w) + self.assertEqual(len(ifs), ifs_len) + nw = ifs.weights() + self.assertEqual(tuple(w), nw) + + def test_position(self): + ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY, + pyrpn.const.RESULT_COUNT, + (640,480)) + self.assertEqual(len(ifs), 0) + self.assertEqual(ifs.position(), 0) + self.assertEqual(ifs.position(10), 10) + self.assertEqual(ifs.position(), 10) + for i in range(640*480): + ifs.position(i) + self.assertEqual(ifs.position(), i) + + + def test_run(self): + """ Testing ifs run """ + ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY, + pyrpn.const.RESULT_COUNT, + (256,256)) + ifs.weights([1]) + ifs[0]['X'] = 'A0 13 +' + ifs[0]['Y'] = 'A1 2 +' + xpos = lambda steps: (steps*13)%256 + ypos = lambda steps: (steps*2)%256 + ifs.run(1) + self.assertEqual(ifs.position(), ifs.to_pos(xpos(1),ypos(1))) + steps = 1 + for _ in range(128): + rnd = random.randint(1,128) + steps += rnd + ifs.run(rnd) + self.assertEqual(ifs.position(), + ifs.to_pos(xpos(steps), ypos(steps))) + + + def test_run_rnd(self): + """ Testing ifs run with random if choice, same weights """ + sz = [10000,10000] + ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY, + pyrpn.const.RESULT_COUNT, + sz) + ifs.weights([1,1]) + ifs[0]['X'] = 'A0 1 +' + ifs[0]['Y'] = 'A1 1 -' + ifs[1]['X'] = 'A0 1 -' + ifs[1]['Y'] = 'A0 1 +' + for _ in Progress(256): + ifs.position(ifs.to_pos(*[s//2 for s in sz])) + + steps = 0 + steps_per_loop = 0x1000 + for _ in range(4): + steps += steps_per_loop + ifs.run(steps_per_loop) + coord = ifs.from_pos(ifs.position()) + + distance = [min(c, sz[i]-c) for i, c in enumerate(coord)] + dist = sum(distance)/2 + percent = steps / (dist * 100) + self.assertLess(percent, 1) + + @skipIfNoNumpy() + def test_buffer_protocol(self): + """ Testing buffer protocol on ifs's mmaps """ + sz = [512,512] + ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY, + pyrpn.const.RESULT_COUNT, + sz) + ifs.weights([1,2]) + for rif in ifs: + for expr in rif: + expr.mutate(n_mutations=20) + + buf = np.frombuffer(ifs, dtype=np.uint64) + buf2 = np.frombuffer(ifs[0], dtype=np.uint64) + + for _ in range(512): + pos = random.randint(0, len(buf)) + buf[pos] = random.randint(0, 0xFFFFFFFF) + pos = random.randint(0, len(buf)) + buf2[pos] = random.randint(0, 0xFFFFFFFF) + + self.assertTrue((buf == buf2).all()) + +class TestRPNIFSCoordinates(unittest.TestCase): + """ Testing methods for coordinates <-> position convertions """ + + def test_position_linear(self): + """ Testing linear coordinate convertion methods """ + rif = pyrpn.RPNIFS(pyrpn.const.POS_LINEAR, + pyrpn.const.RESULT_CONST, + (256,), (42,)) + + for pos in range(256): + with self.subTest(rif=rif, position=pos, expt=(pos,)): + res = rif.from_pos(pos) + self.assertEqual(res, (pos,)) + with self.subTest(rif=rif, expt=pos, coord=(pos,)): + res = rif.to_pos(pos) + self.assertEqual(res, pos) + + def test_position_xy(self): + """ Testing XY coordinate convertion methods """ + for _ in Progress(5): + sz = (random.randint(32,128), random.randint(32,128)) + rif = pyrpn.RPNIFS(pyrpn.const.POS_XY, + pyrpn.const.RESULT_CONST, + sz, (42,)) + for pos in range(sz[0]*sz[1]): + coord = rif.from_pos(pos) + expt = (pos % sz[0], pos // sz[0]) + with self.subTest(sz=sz, pos=pos, expt=expt, result=coord): + self.assertEqual(expt, coord) + result = rif.to_pos(*coord) + with self.subTest(sz=sz, coord=coord, expt=pos, result=result): + self.assertEqual(result, pos) + + def test_position_xy_neg(self): + """ Testing XY coordinates with negative values """ + sz = (100,200) + rif = pyrpn.RPNIFS(pyrpn.const.POS_XY, + pyrpn.const.RESULT_CONST, + sz, (42,)) + + for _ in range(1000): + ny = random.randint(-0x1000000, sz[1]) + nx = random.randint(0,sz[0]-1) + pos = rif.to_pos(nx, ny) + coord = rif.from_pos(pos) + self.assertEqual(coord, (nx, (ny%(1<<64))%sz[1])) + + def test_position_xy_random(self): + """ Testing XY coordinate convertion methods (random samples)""" + for _ in Progress(256): + sz = (random.randint(32,4096), random.randint(32,4096)) + rif = pyrpn.RPNIFS(pyrpn.const.POS_XY, + pyrpn.const.RESULT_CONST, + sz, (42,)) + for _ in range(500): + pos = random.randint(0, (sz[0]*sz[1])-1) + coord = rif.from_pos(pos) + expt = (pos % sz[0], pos // sz[0]) + with self.subTest(sz=sz, pos=pos, expt=expt, result=coord): + self.assertEqual(expt, coord) + result = rif.to_pos(*coord) + with self.subTest(sz=sz, coord=coord, expt=pos, result=result): + self.assertEqual(result, pos) + + def test_position_xdim(self): + """ Testing XDIM coordinate convertion methods """ + ndim = 5 + resol = [8 for _ in range(ndim)] + sz = [ndim]+resol + linear_size = 1 + for e in resol: + linear_size *= e + rif = pyrpn.RPNIFS(pyrpn.const.POS_XDIM, + pyrpn.const.RESULT_CONST, + sz, (42,)) + for pos in range(linear_size): + coord = rif.from_pos(pos) + expt = [] + cur = pos + for dim in resol: + elt = cur % dim + cur //= dim + expt.append(elt) + expt = tuple(expt) + with self.subTest(sz=sz, pos=pos, expt=expt, result=coord): + self.assertEqual(expt, coord) + result = rif.to_pos(*coord) + with self.subTest(sz=sz, coord=coord, expt=pos, result=result): + self.assertEqual(result, pos) + + def test_position_xdim_neg(self): + """ Testing XDIM negative position convertion """ + # TODO test negativ for other coordinate systems + ndim = 4 + szlim = (ndim, 10,20,30,40) + rif = pyrpn.RPNIFS(pyrpn.const.POS_XDIM, + pyrpn.const.RESULT_CONST, + szlim, (42,)) + + pos = rif.to_pos(0,0,-5,0) + self.assertEqual(rif.from_pos(pos), (0,0,(-5%(1<<64))%30, 0)) + + def test_position_xdim(self): + """ Testing XDIM coordinate convertion methods (random sampling)""" + for _ in Progress(16): + ndim = random.randint(3,8) + resol = [random.randint(2, 16) for _ in range(ndim)] + sz = [ndim]+resol + linear_size = 1 + for e in resol: + linear_size *= e + rif = pyrpn.RPNIFS(pyrpn.const.POS_XDIM, + pyrpn.const.RESULT_CONST, + sz, (42,)) + for _ in range(2000): + pos = random.randint(0, linear_size-1) + coord = rif.from_pos(pos) + expt = [] + cur = pos + for dim in resol: + elt = cur % dim + cur //= dim + expt.append(elt) + expt = tuple(expt) + with self.subTest(sz=sz, pos=pos, expt=expt, result=coord): + self.assertEqual(expt, coord) + result = rif.to_pos(*coord) + with self.subTest(sz=sz, coord=coord, expt=pos, result=result): + self.assertEqual(result, pos) + + +class TestRPNIFSMutation(unittest.TestCase): + """ Testing mutation of IFS """ + + def test_set_params_empty(self): + """ Testing set_mutation_params() method giving 'params' argument + only without any existing IFS + """ + ifs=pyrpn.RPNIFS(pyrpn.const.POS_XY, pyrpn.const.RESULT_RGB, (100,100,)) + params = list(ifs.mutation_params) + # Note : precision loss using float avoided using "exact" float values + params[0] = (.25,.75) + + ifs.set_mutation_params(params) + + self.assertEqual(params, list(ifs.mutation_params)) + + def test_set_params(self): + """ Testing set_mutation_params() method giving 'params' argument + only without any existing IFS + """ + ifs=pyrpn.RPNIFS(pyrpn.const.POS_XY, pyrpn.const.RESULT_RGB, (100,100,)) + ifs.weights([1,3]) + + params = list(ifs.mutation_params) + params[0] = (.25,.75) + params[5] = tuple([params[5]] * 2) + + ifs.set_mutation_params(params) + self.assertEqual(params, list(ifs.mutation_params)) + + def test_set_params_kwds(self): + """ Testing set_mutation_params() method giving keywords arguments + """ + + kwds = { + 'weight_type': (0, (0.25, 0.75)), + 'mutation_weight_range': (1, (.5,.75)), + 'if_weights': (3, (.125, .75)), + 'if_component_weights': (4, (.125,.25,.75,.375,1/16)), + 'if_mut_params': (5, (1,1,0,0,0,(1/16,1/32,1/64), (1/64,1/16,1/4))) + } + + for name, elts in kwds.items(): + idx, value = elts + ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY, pyrpn.const.RESULT_RGB, + (100,100)) + ifs.weights([1,3]) + params = list(ifs.mutation_params) + with self.subTest(arg_name=name, value=value): + arg = {name:value} + ifs.set_mutation_params(**arg) + + params[idx] = value + self.assertEqual(params, list(ifs.mutation_params)) + + + def test_set_params_override(self): + """ Testing that params argument of set_mutation_params() is overwritten + by keywords argument values + """ + + ifs = pyrpn.RPNIFS(pyrpn.const.POS_XY, pyrpn.const.RESULT_RGB, (100,100)) + ifs.weights([1,3]) + + params = list(ifs.mutation_params) + params[0] = (.25,.75) + wanted = (.75,.25) + + ifs.set_mutation_params(params, weight_type=wanted) + params[0] = wanted + self.assertEqual(params, list(ifs.mutation_params)) diff --git a/tests/tests_rpn_mutate.py b/tests/tests_rpn_mutate.py new file mode 100755 index 0000000..07ad4e8 --- /dev/null +++ b/tests/tests_rpn_mutate.py @@ -0,0 +1,100 @@ +#!/usr/bin/python3 +# Copyright 2020, 2023 Weber Yann +# +# This file is part of rpnifs. +# +# geneifs is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# geneifs is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with geneifs. If not, see . +# + +import random +import unittest + + +try: + from tqdm import tqdm +except (ImportError, NameError) as e: + tqdm = lambda i, *args, **kwargs:iter(i) + +try: + import pyrpn +except (ImportError, NameError) as e: + print("Error importing pyrpn. Try to run make.", + file=sys.stderr) + raise e + +## TODO : test w_add_elt and w_mut_elt + +class TestRpnExprMutation(unittest.TestCase): + + def test_base_mutation(self): + """ Testing mutation without parameters """ + expr = pyrpn.RPNExpr("", 3) + expr.mutate() + expr.mutate(n_mutations=10) + + def test_mutation_params_len(self): + """ Testing some value for parameters checking resulting expr len """ + + tests = ( + ([1,1,0,0,0,(0,0,0),(0,0,0)], [1,0,100]), + ([1,2,1,0,0,(0,0,0),(0,0,0)], [1/3]), + ([1,2,0,1,0,(0,0,0),(0,0,0)], [2/3]), + ([1,3,1,2,0,(0,0,0),(0,0,0)], [2/6]), + ) + + for p_args, f_args in tests: + params = pyrpn.RPNMutationParamsTuple(p_args) + with self.subTest(params=params): + self._generic_mutation_params_lentest(params, *f_args) + + def test_random_int_mutation_params(self): + """ Testing int random values for parameters checking resulting expr len """ + for _ in range(50): + m_params = [random.randint(1,20) for _ in range(4)] + expt_len = (m_params[0]-m_params[1])/sum(m_params) + expt_len = 0 if expt_len < 0 else expt_len + params = [1] + m_params + [(0,0,0),(0,0,0)] + params = pyrpn.RPNMutationParamsTuple(params) + with self.subTest(params=params): + self._generic_mutation_params_lentest(params, expt_len) + + def test_random_float_mutation_params(self): + """ Testing float random values for parameters checking resulting expr len """ + for _ in range(50): + m_params = [random.randint(0,20)/20 for _ in range(4)] + expt_len = (m_params[0]-m_params[1])/sum(m_params) + expt_len = 0 if expt_len < 0 else expt_len + params = [1] + m_params + [(0,0,0),(0,0,0)] + params = pyrpn.RPNMutationParamsTuple(params) + with self.subTest(params=params): + self._generic_mutation_params_lentest(params, expt_len) + + def _generic_mutation_params_lentest(self, params, expt_len, percent_error=.05,n_mut=5000): + + expt_len = int(expt_len*n_mut) + + expr = pyrpn.RPNExpr("", 3) + expr.mutate(n_mut, params) + msg = "Expected a len of %d ( [%d..%d] with %.1f%% error) but got %d" + self.assertTrue(abs(len(expr) - expt_len) <= n_mut*percent_error, + msg=msg % (expt_len, + expt_len + n_mut*percent_error, + expt_len - n_mut*percent_error, + percent_error*100, + len(expr))) + + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/tests_rpn_sequence.py b/tests/tests_rpn_sequence.py new file mode 100755 index 0000000..d707c13 --- /dev/null +++ b/tests/tests_rpn_sequence.py @@ -0,0 +1,121 @@ +#!/usr/bin/python3 +# copyright 2023 weber yann +# +# this file is part of rpnifs. +# +# geneifs is free software: you can redistribute it and/or modify +# it under the terms of the gnu general public license as published by +# the free software foundation, either version 3 of the license, or +# (at your option) any later version. +# +# geneifs is distributed in the hope that it will be useful, +# but without any warranty; without even the implied warranty of +# merchantability or fitness for a particular purpose. see the +# gnu general public license for more details. +# +# you should have received a copy of the gnu general public license +# along with geneifs. if not, see . +# + +import copy +import pickle +import random +import sys + +import unittest + +try: + import pyrpn +except (importerror, nameerror) as e: + print("error importing pyrpn. try to run make.", + file=sys.stderr) + raise e + + +class TestRpnExprCopy(unittest.TestCase): + """ Testing RPNExpr sequence method """ + + + def test_len(self): + """ Testing __len__ on some expressions """ + + tests = ( + (['0x42 + A0', 2], 3), + (['A0', 10], 1), + (['', 1], 0), + ) + + for args, elen in tests: + e = pyrpn.RPNExpr(*args) + with self.subTest(expr=e, expected_len=elen): + self.assertEqual(len(e), elen) + + + def test_rand_len(self): + """ Testing __len__ with random expressions """ + for _ in range(100): + elen = random.randint(1,50) + rexpr = pyrpn.random_expr(30,elen) + expr = pyrpn.RPNExpr(rexpr, 30) + with self.subTest(expr=expr, expected_len=elen): + self.assertEqual(len(expr), elen) + + def test_getter(self): + """ Test __getitem__ """ + tests = ((['0x42', '+', 'A0'], 1), + (['A0'], 1)) + for test in tests: + elts, nargs = test + expr_str = ' '.join(elts) + expr = pyrpn.RPNExpr(expr_str, nargs) + with self.subTest(expr=expr): + for i in range(len(expr)): + self.assertEqual(expr[i], + pyrpn.tokens.Token.from_str(elts[i])) + + def test_setter(self): + """ Testing __setitem__ """ + expr = pyrpn.RPNExpr('A0 A1 +', 2) + for _ in range(1024): + a0, a1 = (random.randint(0, 0x10000) for _ in range(2)) + res = expr.eval(a0, a1) + self.assertEqual(res, a0+a1) + + expr[2] = pyrpn.tokens.Operand('*') + self.assertEqual(str(expr), 'A0 A1 *') + for _ in range(1024): + a0, a1 = (random.randint(0, 0x1000) for _ in range(2)) + res = expr.eval(a0, a1) + self.assertEqual(res, a0*a1) + + def test_setter_append(self): + """ Testing __setitem__ to append new token """ + expr = pyrpn.RPNExpr('A0 A1', 2) + expr[2] = pyrpn.tokens.Operand('+') + for _ in range(1024): + a0, a1 = (random.randint(0, 0x10000) for _ in range(2)) + res = expr.eval(a0, a1) + self.assertEqual(res, a0+a1) + + def test_setter_err_check_arg(self): + """ Testing __setitem__ errors on invalid argument number""" + for argcnt in range(1,10): + expr = pyrpn.RPNExpr('0x0', argcnt) + for argno in range(argcnt): + expr[0] = pyrpn.tokens.Argument(argno) + for argno in range(argcnt, 0x100): + with self.assertRaises(ValueError): + expr[0] = pyrpn.tokens.Argument(argno) + + def test_setter_err_check_idx(self): + """ Testing __setitem__ errors on invalid index""" + for elen in range(100): + expr = pyrpn.RPNExpr(pyrpn.random_expr(2, elen), 2) + for pos in range(len(expr)+1, 0x100): + with self.assertRaises(IndexError): + expr[pos] = pyrpn.tokens.Value(0x1) + + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/tests_rpn_tokens.py b/tests/tests_rpn_tokens.py new file mode 100755 index 0000000..0624572 --- /dev/null +++ b/tests/tests_rpn_tokens.py @@ -0,0 +1,162 @@ +#!/usr/bin/python3 +# Copyright 2023 Weber Yann +# +# This file is part of rpnifs. +# +# geneifs is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# geneifs is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with geneifs. If not, see . +# + +import sys +import random + +import unittest + +try: + import pyrpn +except (ImportError, NameError) as e: + print("Error importing pyrpn. Try to run make.", + file=sys.stderr) + raise e + + +class TestRpnToken(unittest.TestCase): + """ Testing tokens submodule """ + + def test_token_str_convertion(self): + """ Testing token <-> str convvertion """ + expl = ('+', '-', '&', + 'A1', 'A0', 'A1337', + '0x0', '0x1312') + + for estr in expl: + t = pyrpn.tokens.Token.from_str(estr) + with self.subTest(str=estr, token=t): + self.assertEqual(str(t), estr) + +class TestRpnTokenOperand(unittest.TestCase): + """ Testing operand tokens """ + + def test_str_init(self): + """ Testing operand token initialization """ + test_str = ('add', '+', '-', 'sub', 'and', 'xor') + for op in test_str: + t = pyrpn.tokens.Operand(op) + with self.subTest(opstr=op, token=t): + self.assertIn(str(t), [t.chr(), t.str()]) + + def test_int_init(self): + """ Testing operand token initialization with all opcodes """ + for i in range(pyrpn.tokens.Operand.opcode_max()+1): + t = pyrpn.tokens.Operand(i) + with self.subTest(opcode=i, token=t): + self.assertEqual(i, t.opcode) + + def test_badarg_init(self): + """ Testing operand token initialization with bad argument """ + badargs = ('cxwccseuhefsdfiyg', -1, -150, + pyrpn.tokens.Operand.opcode_max()+1, + pyrpn.tokens.Operand.opcode_max()*2) + badtypes = ('0x12', 'A1', + dict(), tuple(), b'+') + + for badarg in badargs: + with self.subTest(badarg=badarg): + with self.assertRaises(ValueError): + t = pyrpn.tokens.Operand(badarg) + + for badtype in badtypes: + with self.subTest(badtype=badtype): + with self.assertRaises(TypeError): + t = pyrpn.tokens.Operand(badtype) + +class TestRpnTokenValue(unittest.TestCase): + """ Testing value tokens """ + + def test_init(self): + """ Testing value token initialization """ + for _ in range(1000): + rnd = random.randint(0,(1<<64) -1 ) + t = pyrpn.tokens.Value(rnd) + with self.subTest(token=t, value=rnd): + self.assertEqual(int(str(t), 16), rnd) + + def test_badarg_init(self): + """ Testing value token initialization with bad argument """ + badtypes = ('A1', 'add', '+', dict(), tuple(), 5.0) + for badtype in badtypes: + with self.subTest(badarg=badtype): + with self.assertRaises(TypeError): + t = pyrpn.tokens.Value(badtype) + self.assertEqual(int(str(t), 16), badtype) + + def test_badarg_overflow(self): + """ Testing value token initialization overflow """ + badtypes = (1<<64, -1) + for badtype in badtypes: + with self.subTest(badarg=badtype): + with self.assertRaises(OverflowError): + t = pyrpn.tokens.Value(badtype) + self.assertEqual(int(str(t), 16), badtype) + +class TestRpnTokenArgument(unittest.TestCase): + """ Testing argument tokens """ + + def test_init(self): + """ Testing argument token initialization """ + for _ in range(1000): + rnd = random.randint(0,(1<<64) -1 ) + t = pyrpn.tokens.Argument(rnd) + with self.subTest(token=t, argument=rnd): + self.assertEqual(int(str(t)[1:],10), rnd) + + def test_badarg_init(self): + """ Testing argument token initialization with bad argument """ + badtypes = ('0x1', 'add', '+', dict(), tuple(), 5.0) + for badtype in badtypes: + with self.subTest(badarg=badtype): + with self.assertRaises(TypeError): + t = pyrpn.tokens.Argument(badtype) + self.assertEqual(int(str(t), 16), badtype) + + def test_badarg_overflow(self): + """ Testing argument token initialization overflow """ + badtypes = (1<<64, -1) + for badtype in badtypes: + with self.subTest(badarg=badtype): + with self.assertRaises(OverflowError): + t = pyrpn.tokens.Argument(badtype) + self.assertEqual(int(str(t), 16), badtype) + + +#TODO move in a file dedicated to RPNExpr checks +class TestTokenExprRepr(unittest.TestCase): + """ Test RPNExpr sequence implementation """ + + def test_rpnexpr_sequence(self): + """ Testing sequence implementation of RPNExpr """ + for _ in range(100): + argc = random.randint(0,100) + token_count = random.randint(0, 200) + expr_str = pyrpn.random_expr(argc, token_count) + expr = pyrpn.RPNExpr(expr_str, argc) + with self.subTest(argc=argc, token_count=token_count, + expr=expr, expr_str=expr_str): + self.assertEqual(token_count, len(expr)) + self.assertEqual(' '.join([str(token) for token in expr]), + expr_str) + + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/tests_rpniter.py b/tests/tests_rpniter.py new file mode 100755 index 0000000..0759836 --- /dev/null +++ b/tests/tests_rpniter.py @@ -0,0 +1,679 @@ +#!/usr/bin/python3 +# copyright 2023 weber yann +# +# this file is part of rpnifs. +# +# geneifs is free software: you can redistribute it and/or modify +# it under the terms of the gnu general public license as published by +# the free software foundation, either version 3 of the license, or +# (at your option) any later version. +# +# geneifs is distributed in the hope that it will be useful, +# but without any warranty; without even the implied warranty of +# merchantability or fitness for a particular purpose. see the +# gnu general public license for more details. +# +# you should have received a copy of the gnu general public license +# along with geneifs. if not, see . +# + +import copy +import mmap +import pickle +import random +import sys + +import unittest + +try: + import pyrpn +except (ImportError, NameError) as e: + print("error importing pyrpn. try to run make.", + file=sys.stderr) + raise e + +from utils import * + + +class TestRpnExprCopy(unittest.TestCase): + """ Testing RPNExpr sequence method """ + + +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) + + def test_shape(self): + """ Test the shape method """ + tests = [((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_LINEAR, + pyrpn.const.RESULT_RGBA, + (1024,)), (1024,4)), + ((pyrpn.const.POS_XDIM, + pyrpn.const.RESULT_BOOL, + (2,640,480)), (640,480)), + ((pyrpn.const.POS_XDIM, + pyrpn.const.RESULT_RGB, + (5,13,37,13,12,42)), (13,37,13,12,42,3)), + ] + for args, expt in tests: + with self.subTest(args=args, expt_shape=expt): + rif = pyrpn.RPNIterExpr(*args) + self.assertEqual(rif.shape(), expt) + + def test_pickling(self): + """ Test pickling/unpickling """ + tests = [((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_LINEAR, + pyrpn.const.RESULT_RGBA, + (1024,)), (1024,4)), + ((pyrpn.const.POS_XDIM, + pyrpn.const.RESULT_BOOL, + (2,640,480)), (640,480)), + ((pyrpn.const.POS_XDIM, + pyrpn.const.RESULT_RGB, + (5,13,37,13,12,42)), (13,37,13,12,42,3)), + ] + for args, expt in tests: + for _ in range(10): + rif = pyrpn.RPNIterExpr(*args) + for ex in rif.expressions: + ex.mutate(n_mutations = 15) + with self.subTest(rif=rif): + st = pickle.dumps(rif) + rif2 = pickle.loads(st) + st2 = pickle.dumps(rif2) + self.assertEqual(st, st2) + + @skipIfNoNumpy() + def test_buffer(self): + """ Test the len on 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 + + @skipIfNoNumpy() + def test_mmap_accessor(self): + """ Testing mmap access """ + args = (pyrpn.const.POS_XY, + pyrpn.const.RESULT_COUNT, + (640,480)) + rif = pyrpn.RPNIterExpr(*args) + arr = np.frombuffer(rif, dtype=np.uint64) + arr[42] = 1312 + arr2 = np.frombuffer(rif.mmap, dtype=np.uint64) + self.assertTrue((arr == arr2).all()) + + rif.mmap.write(b'hello world !') + arr = np.frombuffer(rif, dtype=np.uint64) + arr2 = np.frombuffer(rif.mmap, dtype=np.uint64) + self.assertTrue((arr == arr2).all()) + + @skipIfNoNumpy() + def test_mmap_argument(self): + """ Testing mmap argument """ + args = (pyrpn.const.POS_XY, + pyrpn.const.RESULT_COUNT, + (640,480)) + params = pyrpn.RPNIterExpr.params(*args) + mm = mmap.mmap(-1, params.memory_size) + mm.write(b'Hello world !') + rif = pyrpn.RPNIterExpr(*args, mmap=mm) + self.assertEqual(mm, rif.mmap) + arr = np.frombuffer(rif, dtype=np.uint64) + arr2 = np.frombuffer(mm, dtype=np.uint64) + self.assertTrue((arr == arr2).all()) + + @skipIfNoNumpy() + def test_mmap_update(self): + """ Testing mmap update """ + args = (pyrpn.const.POS_XY, pyrpn.const.RESULT_RGB, + (640,480)) + params = pyrpn.RPNIterExpr.params(*args) + mm1 = mmap.mmap(-1, params.memory_size) + arr = np.frombuffer(mm1, dtype=np.uint64) + for _ in range(1024): + idx = random.randint(0, params.memory_size // 8) + val = random.randint(0, 0xFFFFFFFF) + arr[idx] = val + + rif = pyrpn.RPNIterExpr(*args) + pos = 0 + while pos == 0: + for exp in rif.expressions: + exp.mutate(n_mutations=15) + pos = rif.step(pos) + + for _ in range(4096): + pos = rif.step(pos) + + arr2 = arrorig = np.frombuffer(rif, dtype=np.uint64) + self.assertFalse((arr == arr2).all()) + + rif.set_mmap(mm1) + arr2 = np.frombuffer(rif, dtype=np.uint64) + self.assertTrue((arr == arr2).all()) + del(arr) + del(arr2) + + mm1.write(b'Hello world !') + arr2 = np.frombuffer(rif, dtype=np.uint64) + arr = np.frombuffer(mm1, dtype=np.uint64) + self.assertTrue((arr == arr2).all()) + + arr = np.frombuffer(mm1, dtype=np.uint64) + self.assertFalse((arr == arrorig).all()) + + def test_str(self): + """ Test string representation of rif """ + + tests = [[pyrpn.const.POS_XY, pyrpn.const.RESULT_RGB, (1024,1024)], + [pyrpn.const.POS_LINEAR, pyrpn.const.RESULT_CONST_RGBA, (1024,), (255,0,0,255)], + [pyrpn.const.POS_XY, pyrpn.const.RESULT_CONST_RGBA, (640,480), (255,0,0,255)], + [pyrpn.const.POS_LINEAR, pyrpn.const.RESULT_RGBA, (1024,)], + [pyrpn.const.POS_XDIM, pyrpn.const.RESULT_RGB, (4, 2, 2, 640, 480)], + [pyrpn.const.POS_XY, pyrpn.const.RESULT_CONST, (1024,512), + (2,)], + ] + for args in tests: + rif = pyrpn.RPNIterExpr(*args) + [expr.mutate(n_mutations=5) for expr in rif] + codes = {spl[1]:spl[2].strip('"') + for spl in [s.split('=') for s in str(rif).split(';') + if len(s.strip())]} + argc = rif.get_params().argc + for key, orig in rif.items(): + expr = pyrpn.RPNExpr(codes[key], argc) + with self.subTest(rif=rif, key=key, orig=orig, clone=expr): + self.assertEqual(orig, expr) + str_repr = repr(rif) + + +class TestRPNIterCoordinates(unittest.TestCase): + """ Testing methods for coordinates <-> position convertions """ + + def test_position_linear(self): + """ Testing linear coordinate convertion methods """ + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_LINEAR, + pyrpn.const.RESULT_CONST, + (256,), (42,)) + + for pos in range(256): + with self.subTest(rif=rif, position=pos, expt=(pos,)): + res = rif.from_pos(pos) + self.assertEqual(res, (pos,)) + with self.subTest(rif=rif, expt=pos, coord=(pos,)): + res = rif.to_pos(pos) + self.assertEqual(res, pos) + + def test_position_xy(self): + """ Testing XY coordinate convertion methods """ + for _ in Progress(5): + sz = (random.randint(32,128), random.randint(32,128)) + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_XY, + pyrpn.const.RESULT_CONST, + sz, (42,)) + for pos in range(sz[0]*sz[1]): + coord = rif.from_pos(pos) + expt = (pos % sz[0], pos // sz[0]) + with self.subTest(sz=sz, pos=pos, expt=expt, result=coord): + self.assertEqual(expt, coord) + result = rif.to_pos(*coord) + with self.subTest(sz=sz, coord=coord, expt=pos, result=result): + self.assertEqual(result, pos) + + def test_position_xy_neg(self): + """ Testing XY coordinates with negative values """ + sz = (100,200) + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_XY, + pyrpn.const.RESULT_CONST, + sz, (42,)) + + for _ in range(1000): + ny = random.randint(-0x1000000, sz[1]) + nx = random.randint(0,sz[0]-1) + pos = rif.to_pos(nx, ny) + coord = rif.from_pos(pos) + self.assertEqual(coord, (nx, (ny%(1<<64))%sz[1])) + + def test_position_xy_random(self): + """ Testing XY coordinate convertion methods (random samples)""" + for _ in Progress(256): + sz = (random.randint(32,4096), random.randint(32,4096)) + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_XY, + pyrpn.const.RESULT_CONST, + sz, (42,)) + for _ in range(500): + pos = random.randint(0, (sz[0]*sz[1])-1) + coord = rif.from_pos(pos) + expt = (pos % sz[0], pos // sz[0]) + with self.subTest(sz=sz, pos=pos, expt=expt, result=coord): + self.assertEqual(expt, coord) + result = rif.to_pos(*coord) + with self.subTest(sz=sz, coord=coord, expt=pos, result=result): + self.assertEqual(result, pos) + + def test_position_xdim(self): + """ Testing XDIM coordinate convertion methods """ + ndim = 5 + resol = [8 for _ in range(ndim)] + sz = [ndim]+resol + linear_size = 1 + for e in resol: + linear_size *= e + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_XDIM, + pyrpn.const.RESULT_CONST, + sz, (42,)) + for pos in range(linear_size): + coord = rif.from_pos(pos) + expt = [] + cur = pos + for dim in resol: + elt = cur % dim + cur //= dim + expt.append(elt) + expt = tuple(expt) + with self.subTest(sz=sz, pos=pos, expt=expt, result=coord): + self.assertEqual(expt, coord) + result = rif.to_pos(*coord) + with self.subTest(sz=sz, coord=coord, expt=pos, result=result): + self.assertEqual(result, pos) + + def test_position_xdim_neg(self): + """ Testing XDIM negative position convertion """ + # TODO test negativ for other coordinate systems + ndim = 4 + szlim = (ndim, 10,20,30,40) + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_XDIM, + pyrpn.const.RESULT_CONST, + szlim, (42,)) + + pos = rif.to_pos(0,0,-5,0) + self.assertEqual(rif.from_pos(pos), (0,0,(-5%(1<<64))%30, 0)) + + def test_position_xdim(self): + """ Testing XDIM coordinate convertion methods (random sampling)""" + for _ in Progress(16): + ndim = random.randint(3,8) + resol = [random.randint(2, 16) for _ in range(ndim)] + sz = [ndim]+resol + linear_size = 1 + for e in resol: + linear_size *= e + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_XDIM, + pyrpn.const.RESULT_CONST, + sz, (42,)) + for _ in range(2000): + pos = random.randint(0, linear_size-1) + coord = rif.from_pos(pos) + expt = [] + cur = pos + for dim in resol: + elt = cur % dim + cur //= dim + expt.append(elt) + expt = tuple(expt) + with self.subTest(sz=sz, pos=pos, expt=expt, result=coord): + self.assertEqual(expt, coord) + result = rif.to_pos(*coord) + with self.subTest(sz=sz, coord=coord, expt=pos, result=result): + self.assertEqual(result, pos) + + +class TestRPNIterConst(unittest.TestCase): + """ Testing various coordinate systems with constant result value """ + + @skipIfNoNumpy() + def test_simple_linear_const(self): + """ Testing a simple constant result on linear coord """ + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_LINEAR, + pyrpn.const.RESULT_CONST, + (512,), (42,)) + + data = np.frombuffer(rif, dtype=np.uint64) + for elt in data: + self.assertEqual(elt, 0) + + rif['X'] = 'A0 A1 +' + pos = rif.step(0) + self.assertEqual(pos, 0) + self.assertEqual(data[0], 42) + + data = np.frombuffer(rif, dtype=np.uint64) + self.assertEqual(data[0], 42) + for elt in data[1:]: + self.assertEqual(elt, 0) + + + + pos = rif.step(0) + self.assertEqual(pos, 42) + + data = np.frombuffer(rif, dtype=np.uint64) + for i, elt in enumerate(data): + with self.subTest(cur_pos=i): + if i in (0, 42): + self.assertEqual(elt, 42) + else: + self.assertEqual(elt, 0) + + + pos = rif.step(1) + self.assertEqual(pos, 1) + pos = rif.step(pos) + self.assertEqual(pos, 43) + + newpos = 512 - 40 + pos = rif.step(newpos) + self.assertEqual(pos, newpos) + pos = rif.step(pos) + self.assertEqual(pos, 2) + + def test_random_linear_const(self): + """ Testing linear coord with const with random expressions """ + for _ in range(200): + const_val = random.randint(0,0xFFFF) + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_LINEAR, + pyrpn.const.RESULT_CONST, + (512,), (const_val,)) + rif['X'].mutate(n_mutations=random.randint(10,100)) + pos=0 + all_pos=[] + for _ in range(100): + pos = rif.step(pos) + all_pos.append(pos) + data = np.frombuffer(rif, dtype=np.uint64) + self.assertEqual(data[pos], const_val) + for i, elt in enumerate(np.frombuffer(rif, dtype=np.uint64)): + if i in all_pos: + self.assertEqual(elt, const_val) + else: + self.assertEqual(elt, 0) + + def test_simple_xy_const(self): + """ Testing xy coord with const value """ + sz = (1024,256) + + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_XY, + pyrpn.const.RESULT_CONST, + sz, (42,)) + + rif['X'] = 'A0 A1 +' + rif['Y'] = 'A2' + + pos = rif.step(0) + self.assertEqual(pos, 0) + + pos = rif.step(0) + self.assertEqual(rif.from_pos(pos), (0,42)) + + pos = rif.step(rif.to_pos(1,1)) + self.assertEqual(rif.from_pos(pos), (2,0)) + + pos = rif.step(rif.to_pos(1,1)) + self.assertEqual(rif.from_pos(pos), (2,0)) + + pos = rif.step(rif.to_pos(2,0)) + self.assertEqual(rif.from_pos(pos), (2,42)) + + def test_random_xy_const(self): + """ Testing xy coord with const with random expressions """ + for _ in Progress(200): + const_val = random.randint(0,0xFFFF) + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_XY, + pyrpn.const.RESULT_CONST, + (1024,1024,), (const_val,)) + rif['X'].mutate(n_mutations=random.randint(10,100)) + rif['Y'].mutate(n_mutations=random.randint(10,100)) + pos=0 + all_pos=[] + for _ in range(100): + sys.stdout.flush() + pos = rif.step(pos) + all_pos.append(pos) + data = np.frombuffer(rif, dtype=np.uint64) + self.assertEqual(data[pos], const_val) + expt = np.zeros(len(data), dtype=np.uint64) + for p in all_pos: + expt[p] = const_val + self.assertTrue((expt == data).all()) + + def test_simple_xdim_const(self): + """ Testing xdim coord with const """ + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_XDIM, + pyrpn.const.RESULT_CONST, + (4,100,200,300,400), (42,)) + rif['D0'] = 'A1' + rif['D1'] = 'A0 A2 +' + rif['D2'] = 'A0 A1 -' + rif['D3'] = 'A3 A4 +' + + pos = rif.step(0) + self.assertEqual(pos, 0) + pos = rif.step(0) + self.assertEqual(rif.from_pos(pos), (0,0,0,42)) + + pos = rif.step(5) + self.assertEqual(rif.from_pos(pos), (0,5,5,0)) + pos = rif.step(rif.to_pos(0,5,5,0)) + self.assertEqual(rif.from_pos(pos), (5,5,(-5%(1<<64))%300,42)) + expt = (5,5,-5,42) + expt_pos = rif.to_pos(*expt) + self.assertEqual(pos, expt_pos) + + def test_linear_rgb(self): + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_LINEAR, + pyrpn.const.RESULT_RGB, + (512,)) + rif['X'] = 'A0 2 +' + rif['R'] = 'A0 1 +' + rif['G'] = 'A1 A2 + 255 %' + rif['B'] = 'A2 2 * A3 +' + + pos = rif.step(0) + self.assertEqual(pos, 2) + data = np.frombuffer(rif, dtype=np.uint64).reshape(rif.shape()) + colors = list(data[2]) + self.assertEqual(colors, [1,0,0]) + + pos = rif.step(2) + self.assertEqual(pos, 4) + data = np.frombuffer(rif, dtype=np.uint64).reshape(rif.shape()) + colors = list(data[4]) + self.assertEqual(colors, [3, 1, 0]) + + def test_linear_rgba(self): + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_LINEAR, + pyrpn.const.RESULT_RGBA, + (512,)) + rif['X'] = 'A0 2 +' + rif['R'] = 'A0 1 +' + rif['G'] = 'A1 A2 + 255 %' + rif['B'] = 'A2 2 * A3 +' + rif['A'] = 'A1 A2 A3 + +' + + pos = rif.step(0) + self.assertEqual(pos, 2) + data = np.frombuffer(rif, dtype=np.uint64).reshape(rif.shape()) + colors = list(data[2]) + self.assertEqual(colors, [1,0,0,0]) + + pos = rif.step(2) + self.assertEqual(pos, 4) + data = np.frombuffer(rif, dtype=np.uint64).reshape(rif.shape()) + colors = list(data[4]) + self.assertEqual(colors, [3, 1, 0, 1]) + + pos = rif.step(4) + self.assertEqual(pos, 6) + data = np.frombuffer(rif, dtype=np.uint64).reshape(rif.shape()) + colors = list(data[6]) + self.assertEqual(colors, [5, 4, 2, 4]) + + pos = rif.step(6) + self.assertEqual(pos, 8) + data = np.frombuffer(rif, dtype=np.uint64).reshape(rif.shape()) + colors = list(data[8]) + self.assertEqual(colors, [7, 9, 10, 11]) + + +class TestRpnIterMutate(unittest.TestCase): + """ @todo Complete tests + """ + + def test_mutate(self): + """ Testing default mutation parameters """ + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_LINEAR, + pyrpn.const.RESULT_RGBA, + (512,)) + + rif.mutate() + rif.mutate(10) + + def test_mutate_weights(self): + """ Testing mutation expression weights """ + params = [1,1,0,0,0,(0,0,0),(0,0,0)] + param = pyrpn.RPNMutationParamsTuple(params) + for n_mut in range(0,100,10): + for i in range(4): + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_LINEAR, + pyrpn.const.RESULT_RGB, + (512,)) + weights = [1 if i == j else 0 for j in range(4)] + rif.mutate(n_mut, params=param, weights=weights) + rpn_exprs = list(rif.values()) + for j in range(4): + with self.subTest(params=param, weights=weights, expr=j): + if j == i: + self.assertEqual(len(rpn_exprs[j]), n_mut) + else: + self.assertEqual(len(rpn_exprs[j]), 0) + n_mut = 400 + for w in Progress(range(100)): + weights = [w for _ in range(4)] + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_LINEAR, + pyrpn.const.RESULT_RGB, + (512, )) + + rif.mutate(n_mut, params=param, weights=weights) + rpn_exprs = list(rif.values()) + for rpnexpr in rpn_exprs: + expr_len = len(rpnexpr) + expt_len = n_mut / 4 + score = abs(1 - (expt_len / expr_len)) + self.assertLess(score, 0.5) + + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/tests_rpniter_seq.py b/tests/tests_rpniter_seq.py new file mode 100755 index 0000000..077d4ed --- /dev/null +++ b/tests/tests_rpniter_seq.py @@ -0,0 +1,100 @@ +#!/usr/bin/python3 +# copyright 2023 weber yann +# +# this file is part of rpnifs. +# +# geneifs is free software: you can redistribute it and/or modify +# it under the terms of the gnu general public license as published by +# the free software foundation, either version 3 of the license, or +# (at your option) any later version. +# +# geneifs is distributed in the hope that it will be useful, +# but without any warranty; without even the implied warranty of +# merchantability or fitness for a particular purpose. see the +# gnu general public license for more details. +# +# you should have received a copy of the gnu general public license +# along with geneifs. if not, see . +# + +import copy +import pickle +import random +import sys + +import unittest + +try: + import pyrpn +except (ImportError, NameError) as e: + print("error importing pyrpn. try to run make.", + file=sys.stderr) + raise e + + +class TestRpnIterSequence(unittest.TestCase): + """ Testing RPNIterExpr sequence methods """ + + def test_len(self): + """ Testing RPNIterExpr.__len__ with various values/constants """ + tests = [([pyrpn.const.POS_XY, pyrpn.const.RESULT_RGB, (1024,1024)], + 5), + ([pyrpn.const.POS_LINEAR, pyrpn.const.RESULT_CONST_RGBA, (1024,), (255,0,0,255)], + 1), + ([pyrpn.const.POS_XY, pyrpn.const.RESULT_CONST_RGBA, (640,480), (255,0,0,255)], + 2), + ([pyrpn.const.POS_LINEAR, pyrpn.const.RESULT_RGBA, (1024,)], + 5), + ([pyrpn.const.POS_XDIM, pyrpn.const.RESULT_RGB, (4, 2, 2, 640, 480)], + 7), + ] + for args, expt_len in tests: + rif=pyrpn.RPNIterExpr(*args) + with self.subTest(rif=rif, expt_len=expt_len, args=args): + self.assertEqual(expt_len, len(rif)) + + + +class TestRPNIterMapping(unittest.TestCase): + """ Testing RPNIterExpr mapping methods """ + + + def test_keys(self): + """ Testing RPNIterExpr.__getitem__ with various values/constants """ + tests = [([pyrpn.const.POS_XY, pyrpn.const.RESULT_RGB, (1024,1024)], + ['X', 'Y', 'R', 'G', 'B']), + ([pyrpn.const.POS_LINEAR, pyrpn.const.RESULT_CONST_RGBA, (1024,), (255,0,0,255)], + ['X']), + ([pyrpn.const.POS_XY, pyrpn.const.RESULT_CONST_RGBA, (640,480), (255,0,0,255)], + ['X', 'Y']), + ([pyrpn.const.POS_LINEAR, pyrpn.const.RESULT_RGBA, (1024,)], + ['X', 'R', 'G', 'B', 'A']), + ([pyrpn.const.POS_XDIM, pyrpn.const.RESULT_RGB, (4, 2, 2, 640, 480)], + ['D0', 'D1', 'D2', 'D3', 'R', 'G', 'B']), + ] + for args, keys in tests: + rif=pyrpn.RPNIterExpr(*args) + for curkey in keys: + with self.subTest(rif=rif, key=curkey): + expr = rif[curkey] + expt = set(keys) + self.assertEqual(set(rif.keys()), expt) + for key, value in rif.items(): + with self.subTest(item=(key, value), id1=id(rif[key]), + id2=id(value), rif=rif): + self.assertEqual(rif[key], value) + + def test_assignement(self): + """ Testing RPNIterExpr.__setitem__ """ + rif = pyrpn.RPNIterExpr(pyrpn.const.POS_XY, pyrpn.const.RESULT_RGB, (1024,1024)) + rif['X'] = 'A0 A1 +' + for _ in range(1000): + a, b = [random.randint(0,0xFFFF) for _ in range(2)] + args = (a,b, 0,0,0) + self.assertEqual(a+b, rif['X'].eval(*args)) + self.assertEqual(0, rif['Y'].eval(*args)) + + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/tests_tokens.py b/tests/tests_tokens.py new file mode 100755 index 0000000..98b1f68 --- /dev/null +++ b/tests/tests_tokens.py @@ -0,0 +1,92 @@ +#!/usr/bin/python3 +# copyright 2023 weber yann +# +# this file is part of rpnifs. +# +# geneifs is free software: you can redistribute it and/or modify +# it under the terms of the gnu general public license as published by +# the free software foundation, either version 3 of the license, or +# (at your option) any later version. +# +# geneifs is distributed in the hope that it will be useful, +# but without any warranty; without even the implied warranty of +# merchantability or fitness for a particular purpose. see the +# gnu general public license for more details. +# +# you should have received a copy of the gnu general public license +# along with geneifs. if not, see . +# +import random +import sys + +import unittest + +try: + import pyrpn +except (ImportError, NameError) as e: + print("error importing pyrpn. try to run make.", + file=sys.stderr) + raise e + + +class TestTokens(unittest.TestCase): + """ Testing tokens module """ + + def test_argument(self): + """ Testing tokens.Argument type init """ + for argno in range(1024): + arg = pyrpn.tokens.Argument(argno) + with self.subTest(argno=argno, arg=arg): + self.assertEqual(arg.argno, argno) + + def test_value(self): + """ Testing tokens.Value type init """ + for _ in range(1024): + v = random.randint(0, 0xFFFFFFFF) + val = pyrpn.tokens.Value(v) + with self.subTest(val=v, token_value=val): + self.assertEqual(val.value, v) + + def test_op(self): + """ Testing tokens.Operand type init """ + for opcode in range(pyrpn.tokens.Operand.opcode_max()+1): + operand = pyrpn.tokens.Operand(opcode) + with self.subTest(opcode=opcode, operand=operand): + self.assertEqual(opcode, operand.opcode) + + op_fromstr = pyrpn.tokens.Operand(str(operand)) + with self.subTest(from_str=op_fromstr, opcode=opcode): + self.assertEqual(opcode, op_fromstr.opcode) + + def test_cmp(self): + """ Testing token comparison """ + tests = { + 'lt': ([('A0', 'A1'), ('0x10', '0x11'), ('-', '/'), + ('-', 'A0'), ('-', '0x1'), ('A0', '0x1')], + [('A1', 'A0'), ('0x11', '0x10'), ('/', '-'), + ('A0', '-'), ('0x1000', '+'), ('0x0', 'A1')]), + 'eq': [[('A0', 'A0'), ('0x1', '0x1'), ('+', '+')], + []] + } + tests['gt'] = (tests['lt'][1], tests['lt'][0]) + tests['le'] = [tests['lt'][i] + tests['eq'][i] for i in range(2)] + tests['ge'] = [tests['gt'][i] + tests['eq'][i] for i in range(2)] + tests['eq'][1] = [('A0', '0x0'), ('A0', '+'), ('0x0', '+')] + + assertions = {'lt': (self.assertLess, self.assertGreaterEqual), + 'le': (self.assertLessEqual, self.assertGreater), + 'eq': (self.assertEqual, self.assertNotEqual), + 'gt': (self.assertGreater, self.assertLessEqual), + 'ge': (self.assertGreaterEqual, self.assertLess)} + + for comparison, (cmp_true, cmp_false) in tests.items(): + assert_true, assert_false = assertions[comparison] + lst = [(assert_true, cmp_true), (assert_false, cmp_false)] + for assertion, cmplist in lst: + for test in cmplist: + a=pyrpn.tokens.Token.from_str(test[0]) + b=pyrpn.tokens.Token.from_str(test[1]) + with self.subTest(assertion=assertion, a=a, b=b): + assertion(a, b) + + diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..8c95d35 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,204 @@ +#!/usr/bin/python3 +# copyright 2023 weber yann +# +# this file is part of rpnifs. +# +# geneifs is free software: you can redistribute it and/or modify +# it under the terms of the gnu general public license as published by +# the free software foundation, either version 3 of the license, or +# (at your option) any later version. +# +# geneifs is distributed in the hope that it will be useful, +# but without any warranty; without even the implied warranty of +# merchantability or fitness for a particular purpose. see the +# gnu general public license for more details. +# +# you should have received a copy of the gnu general public license +# along with geneifs. if not, see . +# + +from ctypes import * +import struct +import sys +import math + +try: + import pyrpn +except (ImportError, NameError) as e: + print("error importing pyrpn. try to run make.", + file=sys.stderr) + raise e + + +VERBOSE = '-v' in sys.argv or '--verbose' in sys.argv + +class Progress: + + STYLE = '|/-\\' + + def __init__(self, iterator, count=None): + if isinstance(iterator, int): + self.__iter = range(iterator) + self.__count = iterator + else: + self.__iter = iterator + try: + self.__count = len(iterator) if count is None else count + except Exception: + self.__count = None + self.__iter = iter(self.__iter) + + self.__cur = 0 + if self.__count is not None: + count_sz = math.floor(math.log10(self.__count)+1) + self.__fmt = '%%%dd/%%%dd' % (count_sz, count_sz) + + def __iter__(self): + return self + + def __next__(self): + return self.next() + + def next(self): + if not VERBOSE or self.__count is None: + clean_len = 1 + if VERBOSE and self.__count == 0: + sys.stderr.write('\b') + sys.stderr.write(self.STYLE[self.__cur%len(self.STYLE)]) + sys.stderr.write('\b') + else: + msg = self.__fmt % (self.__cur+1, self.__count) + clean_len = len(msg) + msg += '\b' * clean_len + sys.stderr.write(msg) + sys.stderr.flush() + self.__cur += 1 + try: + return next(self.__iter) + except Exception as expt: + sys.stderr.write(' '*clean_len + '\b' * clean_len) + raise expt + + +def RPNExpr_pickle_bin_dump(state): + """ Return a dict containing rpn state dump fields + """ + ser_fields = ['token_sz', 'stack_sz', 'argc', 'state'] + ser_expr = struct.unpack('QQQQ', state[:32]) + ser_expr = dict(zip(ser_fields, ser_expr)) + err = state[32:32+128] + if err == b'\x00' * 128: + err = None + ser_expr['err_reason'] = err + + token_sz = 24 + tok_start = 32+128 + tok_end = tok_start + (token_sz * ser_expr['token_sz']) + + tokens = [struct.unpack('QQQ', state[tok_start+(24*i):tok_start+(24*(i+1))]) + for i in range(ser_expr['token_sz'])] + + tok_types = ['op', 'arg', 'val'] + #pretty_toks=[{'type': tok_types[tok[0]], + # 'type_raw': tok[0], + # 'v1': hex(tok[1]), + # 'v2': hex(tok[2])} + # for tok in tokens] + pretty_toks = [(tok_types[tok[0]],)+tok for tok in tokens] + + ser_expr['tokens'] = pretty_toks + #ser_expr['tokens'] = tokens + + stack_start = tok_end + stack_end = stack_start + (8*ser_expr['stack_sz']) + if len(state) < stack_end: + print("ERROR expt size = %d but got %d" % (stack_end, len(state))) + ser_expr['stack'] = 'FAULT' + return ser_expr + stack = struct.unpack('L'*ser_expr['stack_sz'], state[stack_start:stack_end]) + ser_expr['stack'] = stack + + return ser_expr + + + +def RPNIterExpr_pickle_bin_dump(dump): + + import pprint + + params_fields = ('mem_sz','value_sz','rpn_sz','rpn_argc','rpn_stack_sz', + 'getarg_f','getres_f','data_ptr') + data_fields = ('pos_flag','res_flag', 'size_lim_ptr', 'ndim', 'const_val_ptr') + + ptr = dump[0:8] + params = dump[8:72] + data = dump[72:104] + + print("PARAMS : %r" % params) + print("DATA : %r" % data) + + params = struct.unpack('LLLLbPPP', params) + params = dict(zip(params_fields, params)) + + data = struct.unpack('hhPLP', data) + data = dict(zip(data_fields, data)) + + params['data'] = data + + sz_sz = data['ndim'] + (1 if data['pos_flag'] == pyrpn.const.POS_XDIM else 0) + sz_end = 104 + 8*sz_sz + sz_lim = struct.unpack('L' * sz_sz, dump[104:sz_end]) + data['sz_lim'] = sz_lim + + const_sz = 0 + if data['res_flag'] == pyrpn.const.RESULT_CONST: + const_sz = 1 + elif data['res_flag'] == pyrpn.const.RESULT_CONST_RGBA: + const_sz = 4 + + const_end = sz_end + (8*const_sz) + + if const_sz: + const_val = struct.unpack('L'*const_sz, dump[sz_end:const_end]) + else: + const_val = [] + + + print("SZLIM : %r" % (dump[104:sz_end])) + print("CONST : %r" % (dump[sz_end:const_end])) + + data['const_val'] = const_val + + rpn_sz_end = const_end + (8*params['rpn_sz']) + + + rpn_sz = struct.unpack('L'*params['rpn_sz'], dump[const_end:rpn_sz_end]) + + ddump = {'params': params, 'rpn_sz': rpn_sz, 'expr': dict()} + + rpn_buf_start = rpn_sz_end + for i in range(params['rpn_sz']): + rpn_buf_end = rpn_buf_start + rpn_sz[i] + rpn_state = dump[rpn_buf_start:rpn_buf_end] + print("RPN#%d[%d:%d] = %r" % (i, rpn_buf_start, rpn_buf_end, rpn_state)) + try: + ddump['expr'][i] = RPNExpr_pickle_bin_dump(rpn_state) + except Exception as expt: + print("ERROR : ", expt) + rpn_buf_start = rpn_buf_end + + return ddump + + +def bindump(data): + + for i in range(0,len(data), 0x10): + print("%02X " % i, data[i:i+0x10]) + if len(data) % 0x10: + last = ((len(data) / 0x10)+1) + print("%02X " % last, data[last:]) + + + + +