From 456d862232200b00e9daa3cc2ed76b8a75ec2d45 Mon Sep 17 00:00:00 2001 From: Richard Preen Date: Tue, 25 Aug 2026 15:08:08 +0100 Subject: [PATCH 1/3] feat: tree-GP max_len enforces maximum evolved tree size --- xcsf/cond_gp.c | 5 ++--- xcsf/gp.c | 37 ++++++++++++++++++++++--------------- xcsf/gp.h | 7 ++++--- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/xcsf/cond_gp.c b/xcsf/cond_gp.c index ae9de05b2..ac544ca73 100644 --- a/xcsf/cond_gp.c +++ b/xcsf/cond_gp.c @@ -17,13 +17,12 @@ * @file cond_gp.c * @author Richard Preen * @copyright The Authors. - * @date 2016--2022. + * @date 2016--2026. * @brief Tree GP condition functions. */ #include "cond_gp.h" #include "ea.h" -#include "sam.h" #include "utils.h" /** @@ -149,7 +148,7 @@ cond_gp_crossover(const struct XCSF *xcsf, const struct Cl *c1, struct CondGP *cond1 = c1->cond; struct CondGP *cond2 = c2->cond; if (rand_uniform(0, 1) < xcsf->ea->p_crossover) { - tree_crossover(&cond1->gp, &cond2->gp); + tree_crossover(&cond1->gp, &cond2->gp, xcsf->cond->targs); return true; } return false; diff --git a/xcsf/gp.c b/xcsf/gp.c index e558a490f..e89345189 100644 --- a/xcsf/gp.c +++ b/xcsf/gp.c @@ -17,7 +17,7 @@ * @file gp.c * @author Richard Preen * @copyright The Authors. - * @date 2016--2022. + * @date 2016--2026. * @brief An implementation of GP trees based upon TinyGP. * @see Poli, Langdon, and McPhee (2008) "A Field Guide to Genetic Programming" */ @@ -26,20 +26,20 @@ #include "sam.h" #include "utils.h" -#define GP_NUM_FUNC (4) //!< Number of selectable GP functions -#define ADD (0) //!< Addition function -#define SUB (1) //!< Subtraction function -#define MUL (2) //!< Multiplication function -#define DIV (3) //!< Division function +#define GP_NUM_FUNC 4 //!< Number of selectable GP functions +#define ADD 0 //!< Addition function +#define SUB 1 //!< Subtraction function +#define MUL 2 //!< Multiplication function +#define DIV 3 //!< Division function -#define STRING_ADD ("+\0") //!< Addition -#define STRING_SUB ("-\0") //!< Subtraction -#define STRING_MUL ("*\0") //!< Multiplication -#define STRING_DIV ("/\0") //!< Division +#define STRING_ADD "+\0" //!< Addition +#define STRING_SUB "-\0" //!< Subtraction +#define STRING_MUL "*\0" //!< Multiplication +#define STRING_DIV "/\0" //!< Division -#define N_MU (1) //!< Number of tree-GP mutation rates -#define RET_MIN (-1000) //!< Minimum tree return value -#define RET_MAX (1000) //!< Maximum tree return value +#define N_MU 1 //!< Number of tree-GP mutation rates +#define RET_MIN -1000 //!< Minimum tree return value +#define RET_MAX 1000 //!< Maximum tree return value /** * @brief Self-adaptation method for mutating GP trees. @@ -313,9 +313,11 @@ tree_copy(struct GPTree *dest, const struct GPTree *src) * @brief Performs sub-tree crossover. * @param [in] p1 The first GP tree to perform crossover. * @param [in] p2 The second GP tree to perform crossover. + * @param [in] args Tree GP parameters. */ void -tree_crossover(struct GPTree *p1, struct GPTree *p2) +tree_crossover(struct GPTree *p1, struct GPTree *p2, + const struct ArgsGPTree *args) { const int len1 = p1->len; const int len2 = p2->len; @@ -324,12 +326,17 @@ tree_crossover(struct GPTree *p1, struct GPTree *p2) const int start2 = rand_uniform_int(0, len2); const int end2 = tree_traverse(p2->tree, start2); const int nlen1 = start1 + (end2 - start2) + (len1 - end1); + const int nlen2 = start2 + (end1 - start1) + (len2 - end2); + + if (nlen1 > args->max_len || nlen2 > args->max_len) { + return; + } + int *new1 = malloc(sizeof(int) * nlen1); memcpy(&new1[0], &p1->tree[0], sizeof(int) * start1); memcpy(&new1[start1], &p2->tree[start2], sizeof(int) * (end2 - start2)); memcpy(&new1[start1 + (end2 - start2)], &p1->tree[end1], sizeof(int) * (len1 - end1)); - const int nlen2 = start2 + (end1 - start1) + (len2 - end2); int *new2 = malloc(sizeof(int) * nlen2); memcpy(&new2[0], &p2->tree[0], sizeof(int) * start2); memcpy(&new2[start2], &p1->tree[start1], sizeof(int) * (end1 - start1)); diff --git a/xcsf/gp.h b/xcsf/gp.h index 183ec801e..753d430a3 100644 --- a/xcsf/gp.h +++ b/xcsf/gp.h @@ -17,7 +17,7 @@ * @file gp.h * @author Richard Preen * @copyright The Authors. - * @date 2016--2022. + * @date 2016--2026. * @brief An implementation of GP trees based upon TinyGP. */ @@ -34,7 +34,7 @@ struct ArgsGPTree { int n_inputs; //!< Number of inputs int n_constants; //!< Number of constants available int init_depth; //!< Initial depth - int max_len; //!< Maximum initial length + int max_len; //!< Maximum length double *constants; //!< Constants available for GP trees }; @@ -71,7 +71,8 @@ double tree_eval(struct GPTree *gp, const struct ArgsGPTree *args, const double *x); void -tree_crossover(struct GPTree *p1, struct GPTree *p2); +tree_crossover(struct GPTree *p1, struct GPTree *p2, + const struct ArgsGPTree *args); bool tree_mutate(struct GPTree *gp, const struct ArgsGPTree *args); From 3cad94bdd6730a50ebe06844dd382e7fea3cd04b Mon Sep 17 00:00:00 2001 From: Richard Preen Date: Tue, 25 Aug 2026 15:13:20 +0100 Subject: [PATCH 2/3] docs: update changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffa47c4ec..c0693b260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ # Changelog +## Unreleased + +Changes: +* Feat: enforce maximum evolved tree-GP size with `max_len` instead of just the initial size ([#245](https://github.com/xcsf-dev/xcsf/pull/245)) + ## Version 1.5.1 (Aug 24, 2026) Changes: -* CI fix macosx_x86_64 wheel building ([#244](https://github.com/xcsf-dev/xcsf/pull/244)) +* CI: fix macosx_x86_64 wheel building ([#244](https://github.com/xcsf-dev/xcsf/pull/244)) ## Version 1.5.0 (Aug 24, 2026) From 10eaac704b5083a5a1bf506af67fab9d371230e2 Mon Sep 17 00:00:00 2001 From: Richard Preen Date: Tue, 25 Aug 2026 15:21:31 +0100 Subject: [PATCH 3/3] add test --- test/cond_gp_test.cpp | 6 +++++- xcsf/cond_gp.c | 3 +-- xcsf/gp.c | 5 +++-- xcsf/gp.h | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/test/cond_gp_test.cpp b/test/cond_gp_test.cpp index 324f88f9f..67b788dec 100644 --- a/test/cond_gp_test.cpp +++ b/test/cond_gp_test.cpp @@ -17,7 +17,7 @@ * @file cond_gp_test.cpp * @author Richard Preen * @copyright The Authors. - * @date 2023--2024. + * @date 2023--2026. * @brief GP condition tests. */ @@ -114,6 +114,10 @@ TEST_CASE("COND_GP") // check trees are different CHECK(!check_array_eq_int(dest_cond->gp.tree, src_cond->gp.tree, n)); + /* Test max_len enforced during crossover */ + tree_param_set_max_len(targs, 2); + CHECK(!cond_gp_crossover(&xcsf, c1, c2)); + /* Smoke test export */ char *json_str = cond_gp_json_export(&xcsf, c1); CHECK(json_str != NULL); diff --git a/xcsf/cond_gp.c b/xcsf/cond_gp.c index ac544ca73..33ba39b48 100644 --- a/xcsf/cond_gp.c +++ b/xcsf/cond_gp.c @@ -148,8 +148,7 @@ cond_gp_crossover(const struct XCSF *xcsf, const struct Cl *c1, struct CondGP *cond1 = c1->cond; struct CondGP *cond2 = c2->cond; if (rand_uniform(0, 1) < xcsf->ea->p_crossover) { - tree_crossover(&cond1->gp, &cond2->gp, xcsf->cond->targs); - return true; + return tree_crossover(&cond1->gp, &cond2->gp, xcsf->cond->targs); } return false; } diff --git a/xcsf/gp.c b/xcsf/gp.c index e89345189..66b821d70 100644 --- a/xcsf/gp.c +++ b/xcsf/gp.c @@ -315,7 +315,7 @@ tree_copy(struct GPTree *dest, const struct GPTree *src) * @param [in] p2 The second GP tree to perform crossover. * @param [in] args Tree GP parameters. */ -void +bool tree_crossover(struct GPTree *p1, struct GPTree *p2, const struct ArgsGPTree *args) { @@ -329,7 +329,7 @@ tree_crossover(struct GPTree *p1, struct GPTree *p2, const int nlen2 = start2 + (end1 - start1) + (len2 - end2); if (nlen1 > args->max_len || nlen2 > args->max_len) { - return; + return false; } int *new1 = malloc(sizeof(int) * nlen1); @@ -348,6 +348,7 @@ tree_crossover(struct GPTree *p1, struct GPTree *p2, p2->tree = new2; p1->len = tree_traverse(p1->tree, 0); p2->len = tree_traverse(p2->tree, 0); + return true; } /** diff --git a/xcsf/gp.h b/xcsf/gp.h index 753d430a3..7650af9ec 100644 --- a/xcsf/gp.h +++ b/xcsf/gp.h @@ -70,7 +70,7 @@ tree_json_import(struct GPTree *gp, const struct ArgsGPTree *args, double tree_eval(struct GPTree *gp, const struct ArgsGPTree *args, const double *x); -void +bool tree_crossover(struct GPTree *p1, struct GPTree *p2, const struct ArgsGPTree *args);