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:])
+
+
+
+
+