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) 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 ae9de05b2..33ba39b48 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,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); - 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 e558a490f..66b821d70 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) +bool +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 false; + } + 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)); @@ -341,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 183ec801e..7650af9ec 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 }; @@ -70,8 +70,9 @@ tree_json_import(struct GPTree *gp, const struct ArgsGPTree *args, double tree_eval(struct GPTree *gp, const struct ArgsGPTree *args, const double *x); -void -tree_crossover(struct GPTree *p1, struct GPTree *p2); +bool +tree_crossover(struct GPTree *p1, struct GPTree *p2, + const struct ArgsGPTree *args); bool tree_mutate(struct GPTree *gp, const struct ArgsGPTree *args);