Compare commits

...
Sign in to create a new pull request.

52 commits

Author SHA1 Message Date
30c6c92493 Preparing implementation & python API for IFS mutation 2024-01-04 19:23:07 +01:00
5b2b9844e8 Implement RpnIterExpr.mutate method
- adds basic tests of the method
- bugfix 0 weights handling
2023-11-28 16:35:51 +01:00
684bb12614 Changes RpnExpr.mutate argument order 2023-11-27 18:39:22 +01:00
d25529c5c6 Implement buffer protocol for RPNIFS class 2023-10-05 11:16:41 +02:00
7c335fcff0 First usable IFS implementation
Implements borrowed if and other needed stuff + tests & benchamrk
2023-10-04 12:17:45 +02:00
48b8f3fbad Starts implementing RPNIFS class
- __init__
- weights setter/getter
- __len__
2023-09-22 14:43:22 +02:00
472803dc60 Squeleton of RPNIFS python class 2023-09-22 14:40:13 +02:00
ca33aa2050 Enhancement in test Makefile 2023-09-18 00:20:00 +02:00
28f0fdbb20 Implemented RPNIterExpr pickle/unpickle methods 2023-09-18 00:19:31 +02:00
059550df46 Implements mmap.mmap usage in RPNIterExpr
Preparing serialization
2023-09-11 14:36:08 +02:00
7179fd5814 Implementing serialization in rpn_jit and use it in python_rpnexpr get/setstate 2023-09-10 17:12:19 +02:00
3eed2b7f3e Implement RPNIterExpr.shape() method + test + todo done 2023-09-09 14:31:11 +02:00
d86a465339 Adds coordinate convertion methods to python lib + tests 2023-09-09 14:30:27 +02:00
5e2aa9971d Bugfix rpn_lib.asm
Frogot that rbx MUST be preserved :/
2023-08-10 17:08:25 +02:00
65d2bd32b1 Add gcov test coverage suppport 2023-08-10 11:28:33 +02:00
077821fc3f Comment & doxygen documentation enhancement 2023-08-09 14:36:11 +02:00
f3b8cc817c Enhancement in method declaration
Write a macro allowing to "declare" method header's argument list
2023-08-08 19:30:08 +02:00
daafebb989 Enhancement & features in python_if + other stuff
- Adds str & repr to RPNIterExpr
- Adds richcompare to RPNExpr
- Adds tests
2023-08-08 19:05:40 +02:00
866987dcf0 Bugfix in __getstate__
After mutation some token has non zero initialized useless data that should not
be copied in state.
2023-08-07 16:21:38 +02:00
8a3bf95172 Continue implementation of RPNExprIter & sequences/mapping methods 2023-08-06 14:46:22 +02:00
a21802d66b Implements expression's token classes for python 2023-07-05 14:51:34 +02:00
7531536af3 Bugfix + tests for expression copy, len & mutations 2023-07-04 12:42:38 +02:00
869f22a3ed Starts to implement sequence methods for RPNExpr
Starting with __len__
Next we have to implement token acces as a sequence in RPNExpr, but to do
this we have to implement a module allowing token access from python.
2023-07-03 12:18:28 +02:00
3c593f2a04 Implements mutation & copy for rpnepxr 2023-06-28 12:11:36 +02:00
65875ab93a Moved named tuple init in pyrpn module and declaration in python_const 2023-06-28 12:07:49 +02:00
f7716ebc03 Adds the security section to instruct ld to protect the stack 2023-06-18 15:55:45 +02:00
fb42b293e8 Preparing mutation with bugfix & enhancement in jit & parse 2023-06-08 11:15:24 +02:00
3097c98237 Add expression accessor for RPNIterExpr 2023-06-07 12:01:58 +02:00
d535b5c64c Starts implementing RPNIterExpr python object 2023-06-07 10:46:52 +02:00
5d0f8519fd Bugfix & enhancement in rpn_if 2023-06-07 10:46:03 +02:00
45c1e4e3a8 Add a class method to RPNExpr for random expr generation 2023-06-07 10:44:16 +02:00
80a107f16a Merge branch 'new_makefile' into python_rpnifs5 2023-05-25 18:20:38 +02:00
e9abbe2565 Better default buffer size in random expr generation 2023-05-25 18:13:01 +02:00
c4e2de2342 Benchmark enhancement 2023-05-25 18:12:42 +02:00
19f87006d6 Small bugfixes 2023-05-25 17:51:08 +02:00
bec9eba520 Makefile enhancement 2023-05-25 17:50:41 +02:00
43457d08a5 Adds almost useless doc 2023-05-25 17:04:40 +02:00
478b60175a Small enhancement in rpn_if* 2023-05-25 17:00:53 +02:00
0c34c6dade Typo fix 2022-07-05 20:08:25 +02:00
efc67909fd Starting benchmarks test enhancement 2022-07-05 20:07:58 +02:00
d2068bf516 Ifs5 implementation enhancement 2022-06-18 23:45:50 +02:00
148afe7adc Enhancement in tests & set op token pointer to null before serialization 2022-02-24 23:18:18 +01:00
e1a0cc90c9 Fixing again the same bug on pickling/unpickling 2021-10-16 14:30:02 +02:00
6bb5186c8f Bugfixing previous bugfix in rpn_jit 2021-10-16 13:33:44 +02:00
4f6bbd30f0 Bugfix in pickling/unpickling 2021-10-15 17:19:55 +02:00
3d0793d7f3 Commenting++ 2021-07-25 12:54:33 +02:00
afeec1e7aa python_rpnifs to_bytes & levenstein distance implementation 2021-07-24 13:22:36 +02:00
2348d6abaf Adds new python_rpnifs tests 2021-07-24 13:22:08 +02:00
3ca2b247ec First python tests on using rpnifs 2021-05-04 23:01:48 +02:00
be1573b7dd Strange commit 2021-05-04 23:00:04 +02:00
3a492e2561 Deleted old constants tests for python modules 2020-10-10 14:42:31 +02:00
7167152f6c Debug + implements IterExpr in python module (rpn_if objects) 2020-10-10 14:40:12 +02:00
65 changed files with 10999 additions and 633 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
*.o
*.so
*.gcda
*.gcno
lcov.info
lcov_html

View file

@ -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

View file

@ -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

32
benchplot.sh Executable file
View file

@ -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

View file

@ -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

4
deploy.sh Normal file
View file

@ -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

168
python_const.c Normal file
View file

@ -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;
}

79
python_const.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef _PYTHON_CONST_H__
#define _PYTHON_CONST_H__
#include "config.h"
#include <errno.h>
#include <stdarg.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#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

File diff suppressed because it is too large Load diff

View file

@ -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

684
python_ifs.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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; i<ifs_self->ifs->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,
&params, &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; i<ifs_self->ifs->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;
}

114
python_ifs.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef _PYTHON_IFS_H__
#define _PYTHON_IFS_H__
#include "config.h"
#include <errno.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#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

702
python_mutation.c Normal file
View file

@ -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; i<len; i++)
{
_parse_float(elt[i], &w[i]);
if(PyErr_Occurred())
{
break;
}
}
ret:
Py_DECREF(fast);
return;
}
int pyrpn_pyobj_to_mutation_params(PyObject* py_params, rpn_mutation_params_t *params)
{
if(!PySequence_Check(py_params))
{
PyErr_SetString(PyExc_TypeError, "The given object is not a a sequence");
return -1;
}
if(PySequence_Size(py_params) != 7)
{
PyErr_SetString(PyExc_ValueError, "The given object is not a RPNMutationParamsTuple nor of length 7");
return -1;
}
PyObject *fast = PySequence_Fast(py_params, "The given object is not a RPNMutationParamsTuple nor a sequence ?");
if(PyErr_Occurred()) { return -1; }
Py_INCREF(fast);
PyObject **elts = PySequence_Fast_ITEMS(fast);
if(!elts || PyErr_Occurred())
{
return -1;
}
params->min_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], &params->w_add);
if(PyErr_Occurred())
{
PyErr_SetString(PyExc_ValueError, "Bad value for .weight_add field");
return -1;
}
_parse_float(elts[2], &params->w_del);
if(PyErr_Occurred())
{
PyErr_SetString(PyExc_ValueError, "Bad value for .weight_del field");
return -1;
}
_parse_float(elts[3], &params->w_mut);
if(PyErr_Occurred())
{
PyErr_SetString(PyExc_ValueError, "Bad value for .weight_mut field");
return -1;
}
_parse_float(elts[4], &params->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; i<w->if_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; i<w->if_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;
}

130
python_mutation.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef _PYTHON_MUTATION_H__
#define _PYTHON_MUTATION_H__
#include "config.h"
#include <errno.h>
#include <float.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#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

View file

@ -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;
}

View file

@ -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);

View file

@ -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; i<toks.tokens_sz;i++)
{
// restore op pointers
toks.tokens[i].op = &(rpn_ops[toks.tokens[i].op_n]);
}
if(rpn_expr_untokenize(expr_self->rpn, &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(&params, &rpn_mutation_params_default,
sizeof(rpn_mutation_params_t));
}
else
{
if(pyrpn_pyobj_to_mutation_params(py_params, &params) < 0)
{
PyErr_SetString(PyExc_ValueError, "Bad value for params arguments");
return NULL;
}
}
for(size_t i=0; i<n_mutations; i++)
{
if(rpn_mutation(&(self->rpn->toks), &params) < 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;
}

View file

@ -16,45 +16,46 @@
* You should have received a copy of the GNU General Public License
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _PYTHON_PYRPN_H__
#define _PYTHON_PYRPN_H__
#ifndef _PYTHON_RPNEXPR_H__
#define _PYTHON_RPNEXPR_H__
#include "config.h"
#include <errno.h>
#include <float.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#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

154
python_rpnifs/__main__.py Executable file
View file

@ -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)

299
python_rpnifs/expr.py Normal file
View file

@ -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 '<RpnExpr sz=%d %r>' % (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

73
python_rpnifs/fractdim.py Normal file
View file

@ -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]

132
python_rpnifs/ifs1.py Normal file
View file

@ -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

89
python_rpnifs/ifs2.py Normal file
View file

@ -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

147
python_rpnifs/ifs3.py Normal file
View file

@ -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

143
python_rpnifs/ifs4.py Normal file
View file

@ -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

213
python_rpnifs/ifs5.py Normal file
View file

@ -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

504
python_rpntoken.c Normal file
View file

@ -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;
}

211
python_rpntoken.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef _PYTHON_RPNTOKEN_H__
#define _PYTHON_RPNTOKEN_H__
#include "config.h"
#include "rpn_parse.h"
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#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

View file

@ -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; i<params->rpn_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; i<params->rpn_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; i<rif->params->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; i<rif->params->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;
}

View file

@ -21,6 +21,8 @@
#include "config.h"
#include <assert.h>
#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

View file

@ -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;i<ndim;i++)
{
mem_sz *= lim[i+lim_off];
}
argc = rpn_sz = ndim;
lim_sz *= sizeof(size_t);
const_val_sz = 0;
switch(res_flag)
{
case RPN_IF_RES_BOOL:
rpn_sz += 1;
argc += 1;
value_sz = sizeof(rpn_value_t);
break;
case RPN_IF_RES_COUNT:
argc += 1;
value_sz = sizeof(rpn_value_t);
break;
case RPN_IF_RES_XFUN:
rpn_sz += 1;
argc += 1;
value_sz = sizeof(rpn_value_t);
break;
case RPN_IF_RES_RGB:
rpn_sz += 3;
argc += 3;
value_sz = sizeof(rpn_value_t) * 3;
break;
case RPN_IF_RES_RGBA:
rpn_sz += 4;
argc += 4;
value_sz = sizeof(rpn_value_t) * 4;
break;
case RPN_IF_RES_CONST:
const_val_sz = 1;
argc += 1;
value_sz = sizeof(rpn_value_t);
break;
/*
case RPN_IF_RES_CONST_RGB:
const_val_sz = 3;
argc += 3;
break;
*/
case RPN_IF_RES_CONST_RGBA:
const_val_sz = 4;
argc += 4;
value_sz = sizeof(rpn_value_t) * 4;
break;
default:
fprintf(stderr,
"Invalid result flag for if params : %d\n",
pos_flag);
return NULL;
}
if(const_val_sz && !res_const)
{
fprintf(stderr,
"Missing values when creating if params");
return NULL;
}
else if(!const_val_sz && res_const)
{
//Warning
}
const_val_sz *= sizeof(rpn_value_t);
param_sz = lim_sz + const_val_sz + \
sizeof(rpn_if_param_t) + sizeof(rpn_if_default_data_t);
//Allocating result and setting fields values
res = malloc(param_sz);
if(!res)
{
perror("Unable to alloc iterated function params");
return NULL;
}
data = (rpn_if_default_data_t*)(res + 1);
bzero(res, sizeof(*res));
bzero(data, sizeof(*data));
res->data = 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;i<rgb_imax;i++)
{
values[i] = data->const_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;

View file

@ -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

45
rpn_if_mutate.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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; i<n_mut; i++)
{
size_t rpn_num;
if(_rpn_random_choice(rif->params->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; i<rif->params->rpn_sz; i++)
{
rpn_expr_tokens_updated(&(rif->rpn[i]));
}
return 0;
}

58
rpn_if_mutate.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __rpn_if_mutate__h__
#define __rpn_if_mutate__h__
#include "config.h"
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <sys/random.h>
#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

124
rpn_ifs.c
View file

@ -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; i<count; i++)
{
rifs->rpn_if[i] = rpn_if_new(&(rifs->params), rifs->mem, NULL);
if(!rifs->rpn_if[i])
{
return 0;
}
for(size_t j=0; j<rifs->params.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; i<n; i++)
{
rpn_if_t *cur_if = rifs->if_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;
}

View file

@ -21,11 +21,17 @@
#include "config.h"
#include <assert.h>
#include <string.h>
#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

319
rpn_ifs_mutate.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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; i<res->if_count; i++) { sum += res->w_mut_if[i]; }
sum = sum == 0.0 ? 1.0 : sum;
for(size_t i=res->if_count; i<new_if_count; i++)
{
if(res->if_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; i<new_if_count; i++)
{
memcpy(&(res->if_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<n_mut; i++)
{
if(_rpn_random_choice(2, w->_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; i<n_mut; i++)
{
size_t ret;
if(_rpn_random_choice(ifs->if_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; i<n_mut; i++)
{
size_t ret;
if(_rpn_random_choice(ifs->if_sz, w->_w_if, &ret) < 0)
{
goto err;
}
muts[ret]++;
}
for(size_t i=0; i<n_mut; i++)
{
rnd_t *weights;
if(w->w_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;
}

134
rpn_ifs_mutate.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __rpn_ifs_mutate__h__
#define __rpn_ifs_mutate__h__
#include "config.h"
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <sys/random.h>
#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

236
rpn_jit.c
View file

@ -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; i<op_sz; i++)
{
if(buff_sz - offset < 21)
if(buff_sz - offset < BUFF_ALLOC / 4)
{
buff_sz += 40;
buff_sz += BUFF_ALLOC;
cur = realloc(buff, sizeof(char) * buff_sz);
if(!cur)
{
@ -252,10 +291,167 @@ char* rpn_random(size_t op_sz, size_t args_count)
offset += nchr;
}
}
buff[offset] = '\0';
if(offset)
{
buff[offset-1] = '\0';
}
return buff;
}
size_t rpn_expr_serialize(rpn_expr_t* expr, void *buf, size_t buf_sz)
{
const size_t total_sz = sizeof(rpn_expr_serial_t) + \
(sizeof(rpn_token_t)*expr->toks.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;
}

View file

@ -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

View file

@ -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

View file

@ -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

402
rpn_mutate.c Normal file
View file

@ -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), &params->_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; i<sz; i++)
{
if(weights[i] < 0)
{
errno = EDOM;
return -1;
}
total += weights[i];
}
for(size_t i=0; i<sz; i++)
{
rnd_t w;
if(total == 0)
{
w = (rnd_t)((rnd_t_max / sz)*(i+1));
}
else
{
w = ((((long double)weights[i]) * rnd_t_max)/total);
if(i>0)
{
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<sz; (*res)++)
{
if(weights[*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<max; (*res)++)
{
if(*res * step >= 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]);
}

178
rpn_mutate.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __rpn_mutate__h__
#define __rpn_mutate__h__
#include "config.h"
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <sys/random.h>
#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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef __rpn_mutation__h__
#define __rpn_mutation__h__
#include <stddef.h>
#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

View file

@ -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; i<tokens->tokens_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);

View file

@ -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<rpn_op_sz(); IDX++)
#define foreach_rpn_ops(IDX) for(IDX=0; IDX<RPN_OP_SZ; IDX++)
/**@brief Check if a tokenizer is in error state
* @param tokenizer Pointer on a @ref rpn_tokenizer_s
@ -92,7 +92,7 @@ enum rpn_token_type_e {
/**@brief The token is an argument */
RPN_arg,
/**@brief The token is a value */
RPN_val
RPN_val,
};
/**@brief Represent an expression token (value, argument or operation)
@ -165,6 +165,10 @@ struct rpn_tokenizer_s
* @ingroup rpn_tokenize */
extern const rpn_op_t rpn_ops[];
/**@brief The count of operand (the size of @ref rpn_ops array) */
extern const size_t RPN_OP_SZ;
/**@brief Initialize a tokenizer and a tokenized representation
* @param tokenizer Pointer on a new tokenizer
* @param dst Pointer on a tokenized struct to store generated tokens
@ -205,13 +209,13 @@ void rpn_tokenizer_free(rpn_tokenizer_t *tokenizer);
*/
int rpn_tokenize(const char *token, rpn_token_t *dst, char error[64]);
/**@brief Represented a tokenized expression in a string
/**@brief Represent a tokenized expression in a string
* @param tokens Tokenized expression
* @param long_op If true uses @ref rpn_op_s::str else @ref rpn_op_s::chr
* @return A newly allocated char* that should be deallocated using free()
* @ingroup rpn_tokenize
*/
char* rpn_tokenized_expr(rpn_tokenized_t *tokens, char long_op);
char* rpn_tokenized_expr(const rpn_tokenized_t *tokens, char long_op);
/**@brief Returns NULL or a pointer on corresponding operation infos
* @param token The token we want to match
@ -219,6 +223,13 @@ char* rpn_tokenized_expr(rpn_tokenized_t *tokens, char long_op);
* @ingroup rpn_parse
*/
const rpn_op_t* rpn_match_token(const char* token);
/**@brief Returns NULL or pointer on corresponding operation infos
* @param opcode (index in @ref rpn_ops )
* @return NULL or operation informations
*/
const rpn_op_t* rpn_op_from_opcode(unsigned char opcode);
/**@brief Return -1 or an index corresponding to @ref rpn_ops
* @param token The token we want to match
* @return NULL or operation informations
@ -235,10 +246,20 @@ int rpn_match_token_i(const char* token);
*/
int rpn_match_number(const char* token, unsigned long *result);
/**@brief Stores a token string representation in given buffer
* @param token The token to represent
* @param dst The destination buffer for the string
* @param sz The biffer size
* @return Same as snprintf (the number of chr stored or negative value on error
*/
int rpn_token_snprintf(rpn_token_t *token, char *dst, size_t sz);
/**@brief Get operations list size
* @return number of operations in @ref rpn_ops
*/
size_t rpn_op_sz();
/**@brief Macro version of @ref rpn_op_sz() */
#define RPN_OPS_SZ (sizeof(rpn_ops)/sizeof(rpn_op_t))
/**@page rpn_lang RPN expression syntax
* @brief Howto write an expression
@ -274,7 +295,7 @@ size_t rpn_op_sz();
* Each valid operations are declared in @ref rpn_ops variable (see
* @ref rpn_parse.c for details).
*
* The @ref python_module expose a function pyrpn.get_ops() ( @see pyrpn_ops )
* The @ref pymod_pyrpn expose a function pyrpn.get_ops() ( @see pyrpn_ops )
* returning a dict with long operations as key and short as value.
* \subsubsection rpn_lan_op_internal Internal mechanism
* Operations are done using a loopstack : operands are poped from stack, and

1
send_live.example Normal file
View file

@ -0,0 +1 @@
while [ 1 ]; do for img in ../rpnifs2_*.png; do convert "$img" -alpha off "$(basename $img)"; echo -n '.'; done; echo ""; rsync -c -v ./rpnifs2_*.png root@http.zvirt:/home/www-data/www/rpnifs/live/; sleep 40; done

View file

@ -3,20 +3,21 @@ LD=ld
SOURCES=$(wildcard test_*.c)
OBJS=$(patsubst %.c,%.o, $(SOURCES))
BINARIES=$(patsubst %.o, %, $(OBJS))
DEPS=$(wildcard ../*.o)
all: checks
checks: $(BINARIES)
for test_bin in $(BINARIES); do echo "Running $${test_bin}.c"; ./$$test_bin && echo "OK" || echo "fail"; done
checks: $(BINARIES) $(SOURCES)
for test_bin in $(BINARIES); do echo "Running $${test_bin}.c"; ./$$test_bin && echo "OK" || false; done;
../%.o:
make -C ..
$(MAKE) -C .. `basename "$@"`
%.o: %.c
$(CC) -I.. $(CFLAGS) -c -o $@ $<
%.o: %.c $(DEPS)
$(CC) -I .. $(CFLAGS) -c -o $@ $<
test_%: test_%.o ../rpn_lib.o ../rpn_jit.o ../rpn_parse.o
$(CC) -I.. $(CFLAGS) -o $@ $^
test_rpn: test_rpn.o ../rpn_lib.o ../rpn_jit_cov.o ../rpn_parse_cov.o ../rpn_if_cov.o ../rpn_if_default_cov.o
$(CC) -I.. $(CFLAGS) -o $@ $^ -lgcov
.PHONY: clean

View file

@ -1,10 +1,15 @@
#!/usr/bin/python3
import argparse
import os
import sys
import random
import time
import warnings
def usage():
print("Usage : %s [expr_count [max_iter [argc [sz]]]]" %sys.argv[0],
file=sys.stderr)
try:
import pyrpn
@ -13,14 +18,24 @@ except (ImportError, NameError) as e:
file=sys.stderr)
raise e
#expr_count = 0x200
#max_iter = 0x3000
#argc = 2
#sz = 0x20
expr_count = 5000
max_iter = 0x5000
argc = 2
sz = 0x30
allint = lambda val: int(val, 0)
parser = argparse.ArgumentParser(description="Benchmark RPNExpr vs IFS")
parser.add_argument("-c", "--expr-count", type=allint, default=0x200)
parser.add_argument("-i", "--iterations", type=allint, default=0x5000)
parser.add_argument("-s", "--expr-size", type=allint, default=0x50)
parser.add_argument("-G", "--gnuplot-output", type=str, default=None)
args = parser.parse_args()
expr_count = args.expr_count
max_iter = args.iterations
sz = args.expr_size
argc = 1
gpout=None
if args.gnuplot_output is not None:
gpout = open(args.gnuplot_output, "a")
try:
from tqdm import tqdm
@ -33,14 +48,6 @@ except (ImportError, NameError) as e:
tqr = range
write=True
if len(sys.argv) > 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()

View file

@ -1 +0,0 @@
../pyrpn.so

View file

@ -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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
#
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()

View file

@ -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__':

110
tests/tests_rpn_copy.py Executable file
View file

@ -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 <http://www.gnu.org/licenses/>.
#
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()

View file

@ -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),

361
tests/tests_rpn_ifs.py Executable file
View file

@ -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 <http://www.gnu.org/licenses/>.
#
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))

100
tests/tests_rpn_mutate.py Executable file
View file

@ -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 <http://www.gnu.org/licenses/>.
#
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()

121
tests/tests_rpn_sequence.py Executable file
View file

@ -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 <http://www.gnu.org/licenses/>.
#
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()

162
tests/tests_rpn_tokens.py Executable file
View file

@ -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 <http://www.gnu.org/licenses/>.
#
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()

679
tests/tests_rpniter.py Executable file
View file

@ -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 <http://www.gnu.org/licenses/>.
#
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()

100
tests/tests_rpniter_seq.py Executable file
View file

@ -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 <http://www.gnu.org/licenses/>.
#
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()

92
tests/tests_tokens.py Executable file
View file

@ -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 <http://www.gnu.org/licenses/>.
#
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)

204
tests/utils.py Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
#
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:])