Implement RpnIterExpr.mutate method

- adds basic tests of the method
- bugfix 0 weights handling
This commit is contained in:
Yann Weber 2023-11-28 16:35:51 +01:00
commit 5b2b9844e8
8 changed files with 214 additions and 15 deletions

View file

@ -60,6 +60,10 @@ flag, a result flag and size limits"),
METH_O,
"self, position, /",
"Run an IF given a position and return a new position"),
PYRPN_method("mutate", rpnif_mutate,
METH_VARARGS | METH_KEYWORDS,
"self, n_mutations=1, params=None",
"Mutate the expressions in the IF"),
PYRPN_method("to_pos", rpnif_to_pos,
METH_FASTCALL,
"self, *args, /",
@ -514,6 +518,136 @@ PyObject *rpnif_step(PyObject *self, PyObject* opos)
}
PyObject* rpnif_mutate(PyObject *self, PyObject *args, PyObject *kwargs)
{
PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self;
size_t rpn_sz = expr_self->rif->params->rpn_sz;
char *str_args = "|IOO:RPNExpr.mutate";
char *names[] = {"n_mutations", "params", "weights", NULL};
PyObject *py_params = NULL, *py_weights = NULL;
unsigned int n_mutations = 1;
rpn_mutation_params_t params;
rnd_t *weights = malloc(sizeof(rnd_t)*rpn_sz);
if(!PyArg_ParseTupleAndKeywords(args, kwargs, str_args, names,
&n_mutations, &py_params, &py_weights))
{
goto err;
}
if(!py_weights || py_weights == Py_None)
{
for(size_t i=0; i<rpn_sz; i++)
{
weights[i] = (rnd_t)((rnd_t_max / rpn_sz)*(i+1));
}
}
else
{
// Parsing weights sequence into a weights array suitable
// for rpn_mutate utility function
PyObject *seq = PySequence_Fast(py_weights,
"weights argument must be a sequence");
if(!seq)
{
goto err;
}
if((size_t)PySequence_Fast_GET_SIZE(seq) != rpn_sz)
{
Py_DECREF(seq);
char err[] = "expected weights to be a sequence of \
%ld elements, but got %ld elements";
PyErr_Format(PyExc_TypeError, err,
rpn_sz,
PySequence_Fast_GET_SIZE(seq));
goto err;
}
long double total = 0;
for(size_t i=0; i<rpn_sz; i++)
{
PyObject *elt = PySequence_Fast_GET_ITEM(seq, i);
if(!PyNumber_Check(elt))
{
Py_DECREF(seq);
PyErr_SetString(PyExc_TypeError,
"weights must contains float values");
goto err;
}
total += PyFloat_AsDouble(elt);
if(PyErr_Occurred())
{
Py_DECREF(seq);
goto err;
}
}
for(size_t i=0; i<rpn_sz; i++)
{
if(total == 0)
{
weights[i] = (rnd_t)((rnd_t_max / rpn_sz)*(i+1));
continue;
}
PyObject *py_elt = PySequence_Fast_GET_ITEM(seq, i);
long double elt = PyFloat_AsDouble(py_elt);
weights[i] = (elt*rnd_t_max)/total;
weights[i] += i>0? weights[i-1] : 0;
}
Py_DECREF(seq);
}
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(rpnexpr_pyobj_to_mutation_params(py_params, &params) < 0)
{
if(!PyErr_Occurred())
{
PyErr_Format(PyExc_ValueError,
"Bad value for params arguments : %s",
strerror(errno));
}
goto err;
}
}
for(size_t i=0; i<n_mutations; i++)
{
size_t rpn_num;
if(_rpn_random_choice(rpn_sz, weights, &rpn_num) < 0)
{
PyErr_Format(PyExc_RuntimeError,
"Unable to choose an expression \
randomly : %s", strerror(errno));
goto err;
}
if(rpn_mutation(&(expr_self->rif->rpn[rpn_num].toks),
&params) < 0)
{
PyErr_Format(PyExc_RuntimeError,
"Mutation failed : %s",
strerror(errno));
goto err;
}
rpn_expr_tokens_updated(&(expr_self->rif->rpn[rpn_num]));
}
free(weights);
Py_RETURN_NONE;
err:
free(weights);
return NULL;
}
PyObject *rpnif_to_pos(PyObject *self, PyObject** argv, Py_ssize_t argc)
{
PyRPNIterExpr_t *expr_self = (PyRPNIterExpr_t*)self;

View file

@ -198,6 +198,16 @@ PyObject *rpnif_shape(PyObject *self);
*/
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

View file

@ -27,10 +27,6 @@ static PyMethodDef RPNIFS_methods[] = {
"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("weight", rpnifs_weight,
METH_FASTCALL,
"self, idx, weight=None",
"Get or set a single weight"),
PYRPN_method("position", rpnifs_position,
METH_FASTCALL,
"self, position=None",
@ -318,13 +314,6 @@ err_weights:
}
PyObject *rpnifs_weight(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
PyErr_SetString(PyExc_NotImplementedError, "TODO");
return NULL;
}
PyObject *rpnifs_position(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
PyRPNIFS_t *ifs_self = (PyRPNIFS_t*)self;

View file

@ -86,7 +86,6 @@ PyObject *rpnifs_to_pos(PyObject *self, PyObject** argv, Py_ssize_t argc);
PyObject *rpnifs_from_pos(PyObject *self, PyObject* _pos);
PyObject *rpnifs_weights(PyObject *self, PyObject *const *args, Py_ssize_t nargs);
PyObject *rpnifs_weight(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);

View file

@ -548,7 +548,7 @@ PyObject* rpnexpr_mutate(PyObject* slf, PyObject *args, PyObject *kwds)
{
PyRPNExpr_t *self = (PyRPNExpr_t*)slf;
char *str_args = "|IO:RPNIterExpr.mutate";
char *str_args = "|IO:RPNExpr.mutate";
char *names[] = {"n_mutations", "params", NULL};
PyObject *py_params = NULL;

View file

@ -58,7 +58,16 @@ dprintf(2, "summed weights : %d %d %d %d\n",
}
for(uint8_t i=0; i<3; i++)
{
params->_weights[i+4] = to_weight(total, params->w_add_elt[i]);
rnd_t w;
if(total > 0)
{
w = to_weight(total, params->w_add_elt[i]);
}
else
{
w = to_weight(3,1);
}
params->_weights[i+4] = w;
if(i>0)
{
params->_weights[i+4] += params->_weights[i+3];
@ -73,7 +82,16 @@ dprintf(2, "summed weights : %d %d %d %d\n",
}
for(uint8_t i=0; i<3; i++)
{
params->_weights[i+7] = to_weight(total, params->w_mut_elt[i]);
rnd_t w;
if(total > 0)
{
w = to_weight(total, params->w_mut_elt[i]);
}
else
{
w = to_weight(3, 1);
}
params->_weights[i+7] = w;
if(i>0)
{
params->_weights[i+7] += params->_weights[i+6];

View file

@ -36,6 +36,8 @@ typedef struct rpn_mutation_params_s rpn_mutation_params_t;
/**@brief Type of random values used by mutations */
typedef uint16_t rnd_t;
extern const rnd_t rnd_t_max;
/** @brief Mutation parameters */
struct rpn_mutation_params_s
{

View file

@ -627,6 +627,53 @@ class TestRPNIterConst(unittest.TestCase):
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()