C++ API Reference#

The C++ core implements the data structures and algorithms that power Fracnetics. All classes are defined in the include/ directory.

Population#

class Population#

Manages the evolutionary population of GNP individuals (networks).

The Population class implements the evolutionary framework for Genetic Network Programming (GNP). It provides all necessary operations for evolutionary optimization:

  • Population Management: Initialization and maintenance of multiple network individuals

  • Fitness Evaluation: Applying various fitness functions (accuracy, CartPole, Gymnasium environments)

  • Selection: Tournament selection with elitism (to preserve best individuals)

  • Genetic Operators:

    • Edge mutation (topology changes)

    • Boundary mutation (parameter tuning)

    • Crossover (recombination of network structures)

    • Node addition/deletion (structural evolution)

The population evolves over generations through iterative applying fitness measurments, selection, and mutation operators, which gradually improves the collective fitness and discovering effective network structures for the target problem. A small tutorial can be found here:

https://colab.research.google.com/github/FabianKoehnke/fracnetics/blob/main/notebooks/minExampleCartPole.ipynb

Constructor

inline Population(int seed, const unsigned int _ni, unsigned int _jn, unsigned int _jnf, unsigned int _pn, unsigned int _pnf, bool _fractalJudgment, std::vector<int> _nFeatureValues = {})#

Constructs a Population with specified parameters and initializes all individuals.

This constructor creates a complete GNP population by:

  1. Initializing the random number generator with the given seed

  2. Creating ni Network individuals, each with:

    • jn judgment nodes

    • pn processing nodes

    • Random initial topology and function assignments

    • Optional fractal edge patterns (if fractalJudgment is true)

All individuals share the same random generator (via shared_ptr) to ensure reproducibility and coordinated randomness across the population.

Parameters:
  • seed – Random seed for the generator

  • _ni – Number of individuals to create in the population

  • _jn – Initial number of judgment nodes per individual

  • _jnf – Number of judgment node function types (determines feature selection)

  • _pn – Initial number of processing nodes per individual

  • _pnf – Number of processing node function types (determines action/output)

  • _fractalJudgment – If true, judgment nodes use fractal-based edge patterns; if false, standard edge patterns (see boundaryMutationFractal() for more informations on fractal boundaries)

  • _nFeatureValues – set the number of features values to distinguish between numerical and categorical data

    • for numerical features: set 0 at the i-th feature

    • for categorical features: set the numbers of categories at feature position i. This will be the amount of outgoing edges of a judgment node

    • default is an empty vector and all features are treated as numerical

Member Functions

inline void setAllNodeBoundaries(std::vector<float> &minF, std::vector<float> &maxF)#

Initializes decision boundaries for all judgment nodes in all individuals.

This method sets up the decision boundaries that divide the feature space for every judgment node across the entire population. The initialization process:

  1. Iterates through all individuals in the population

  2. For each individual, examines all inner nodes (judgment and processing nodes)

  3. For judgment nodes (type β€œJ”):

    • Standard mode (if fractalJudgment is false):

      • Sets uniformly spaced boundaries within [minF[f], maxF[f]]

    • Fractal mode (if fractalJudgment is true):

      • Generates random production rule parameters using randomParameterCuts()

      • Calculates fractal lengths based on k_d parameters

      • Sets boundaries according to the fractal pattern within [minF[f], maxF[f]]

Note

minF and maxF must have size β‰₯ jnf (cover all judgment node functions)

Warning

This method should be called once after population initialization and before fitness evaluation to ensure all judgment nodes have valid decision boundaries matching the feature value ranges of the problem domain.

Parameters:
  • minF – Vector of minimum values for each feature dimension (indexed by node function f)

  • maxF – Vector of maximum values for each feature dimension (indexed by node function f)

inline void callTraversePath(const std::vector<std::vector<float>> &X, int dMax)#

Executes network traversal for all individuals on a complete dataset.

This function calls the traversePath() method for each individual in the population, allowing all networks to process the entire feature matrix X and generate decision sequences. For each individual:

  1. Clears previous decisions (stored in member decisions) and resets node usage tracking

  2. Processes each row in X through the network

  3. Records the decision made for each input sample

  4. Tracks which nodes were used during traversal

This is primarily used for batch prediction or analysis of network behavior across the population and not fitness evaluation. The fitness needs to be calculated afterwards using the network member decisions.

The dMax parameter prevents infinite loops by limiting consecutive judgment nodes that can be traversed before forcing a decision.

Parameters:
  • X – Feature matrix where each inner vector represents one sample with multiple features

  • dMax – Maximum consecutive judgment nodes allowed per decision (prevents infinite graph cycles)

template<typename FuncFitness>
inline void applyFitness(FuncFitness &&func)#

Applies a generic fitness function to all individuals in the population.

This is a template function that accepts any callable object (function, lambda, functor) and applies it to each individual network.

Note

This is an internal template used by specialized fitness methods

Template Parameters:

FuncFitness – Callable type that accepts Network& and evaluates fitness

Parameters:

func – Fitness function to apply (must accept Network& parameter)

inline void gymnasium(GymEnvWrapper &env, int dMax, int maxSteps, int maxConsecutiveP, int worstFitness, int seed)#

Evaluates all individuals in an OpenAI Gymnasium-compatible reinforcement learning environment.

This method applies fitGymnasium() to the entire population as reinforcement learning agents in a Gymnasium environment.

Parameters:
  • env – GymEnvWrapper object providing interface to the Gymnasium environment

  • dMax – Maximum consecutive judgment nodes per decision (prevents infinite loops in graph traversal)

  • maxSteps – Maximum episode length (prevents indefinite episodes)

  • maxConsecutiveP – Maximum consecutive processing nodes allowed

  • worstFitness – Fitness value assigned when networks violate constraints

  • seed – Random seed for environment initialization (currently unused in implementation)

inline void cartpole(int dMax, int penalty, int maxSteps, int maxConsecutiveP)#

Evaluates all individuals on the CartPole balancing control problem.

This method applies fitCartpole() to the entire population

Parameters:
  • dMax – Maximum consecutive judgment nodes per decision (prevents infinite loops)

  • penalty – Divisor applied to fitness when constraints are violated

  • maxSteps – Maximum episode length

  • maxConsecutiveP – Maximum consecutive processing nodes allowed

inline void tournamentSelection(int N, int E)#

Performs tournament selection with elitism to create the next generation.

This method implements tournament selection, a standard evolutionary algorithm selection operator that balances selective pressure with diversity. The process:

For each individual in the population (except elite):

  1. Randomly sample N individuals to form a tournament

  2. Select the individual with highest fitness from the tournament

  3. Add the winner to the new population

Elitism (preserving E best individuals):

  1. Identifies the E individuals with highest fitness

  2. Copies them unchanged to the new population

  3. Stores their indices in indicesElite for mutation protection

Statistic calculations:

  • bestFit: Maximum fitness in population (including elite)

  • meanFitness: Average fitness in population

  • minFitness: Minimum fitness in population

Tournament selection provides tunable selective pressure: larger N increases pressure (stronger individuals more likely to be selected), while smaller N maintains diversity. Elitism ensures best solutions are never lost, providing monotonic improvement guarantee.

Note

N β‰₯ 2 for meaningful selection pressure

Note

Population size remains constant at ni

Parameters:
  • N – Tournament size (number of individuals per tournament)

  • E – Elite size (number of best individuals to preserve unchanged)

inline void setElite(int E, const std::vector<Network> &individuals, std::vector<Network> &selection)#

Identifies and preserves the elite individuals in the selection.

This method extracts the E best individuals from the current population and adds them to the selection vector, implementing elitism in the evolutionary process.

Track elite indices: Stores indices where elite individuals are placed in the selection vector (used later to protect them from mutation)

Note

Elite indices are used to protect elite from mutation operations

Parameters:
  • E – Number of elite individuals to preserve

  • individuals – Copy of current population (will be modified during extraction)

  • selection – Reference to new population being constructed (elite will be appended)

inline void callEdgeMutation(float probInnerNodes, float probStartNode, bool justUsedNodes = false, int k = 0)#

Applies edge mutation to all non-elite individuals in the population.

This method implements edge mutation, a topology-modifying operator that changes the connections between nodes in the GNP networks. For each non-elite individual (network) and each node:

  1. Inner nodes (judgment and processing nodes): apply edgeMutation() with probability probInnerNodes

  2. Start node: apply edgeMutation() with probability probStartNode

Edge mutation allows the evolutionary process to explore different network topologies by redirecting execution flow.

Elite protection: Elite individuals (tracked via indicesElite) are excluded from mutation to preserve the best solutions found so far.

Note

adaptToEdgeSize not holds for starnode, because it has only one edge and should be mutated with the same probability as nodes with few edges to allow topology changes.

Warning

tournamentSelection() must have been called to set indicesElite

Parameters:
  • probInnerNodes – Probability (in [0.0, 1.0]) that each edge of inner nodes will be mutated

  • probStartNode – Probability (in [0.0, 1.0]) that the start node’s edge will be mutated

  • justUsedNodes – If true, only applies edge mutation to nodes that were used during traversal (node.used == true). If false, applies to all nodes regardless of usage.

  • adaptToEdgeSize – If true, mutation probability is adapted based on the number of edges (e.g., more edges β†’ lower mutation probability) to prevent excessive disruption in highly connected nodes.

template<typename FuncMutation>
inline void applyBoundaryMutation(FuncMutation &&func, bool justUsedNodes = false)#

Applies a generic boundary mutation function to all judgment nodes in non-elite individuals.

This is a template method that provides a flexible interface for applying various boundary mutation strategies to the population. For each non-elite individual:

  1. Iterates through all judgment nodes (type β€œJ”)

  2. Applies the provided mutation function to each judgment node

The mutation function receives:

  • Reference to the node (for modifying boundaries)

  • additionalMutationParam struct (containing network size or other context)

This template design allows implementing different boundary mutation strategies (uniform, normal, fractal, adaptive) without code duplication.

Elite protection: Elite individuals are excluded from mutation.

Note

This is an internal template used by specialized boundary mutation methods

Template Parameters:

FuncMutation – Callable type that accepts (Node&, const additionalMutationParam&)

Parameters:
  • func – Mutation function to apply to each judgment node

  • justUsedNodes – If true, only applies mutation to judgment nodes that were used during traversal (node.used == true).

inline void callBoundaryMutationUniform(const float probability, bool justUsedNodes = false)#

Applies uniform boundary mutation to all judgment nodes in the population.

This method mutates judgment node boundaries using uniform distribution sampling. Each boundary can be shifted anywhere within its valid range (between adjacent boundaries) with equal probability.

Parameters:
  • probability – Probability (in [0.0, 1.0]) that each boundary will be mutated

  • justUsedNodes – If true, only applies mutation to judgment nodes that were used during traversal (node.used == true).

inline void callBoundaryMutationNormal(const float probability, const float sigma, bool justUsedNodes)#

Applies normal (Gaussian) boundary mutation to all judgment nodes in the population.

This method mutates judgment node boundaries using normal distribution sampling centered at current boundary values.

For each judgment node in each non-elite individual, boundaries are mutated according to Node::boundaryMutationNormal(), which samples from N(current_boundary, sigmaΒ²).

Note

Smaller sigma β†’ more conservative, larger sigma β†’ more exploratory

Parameters:
  • probability – Probability (in [0.0, 1.0]) that each boundary will be mutated

  • sigma – Standard deviation of the normal distribution

  • justUsedNodes – If true, only applies mutation to judgment nodes that were used during traversal (node.used == true).

inline void callBoundaryMutationNetworkSizeDependingSigma(const float probability, const float sigma, bool justUsedNodes)#

Applies normal boundary mutation with sigma adapted to network size.

This method implements adaptive boundary mutation where the mutation strength decreases as network size increases. The adaptive sigma is calculated as:

sigmaNew = sigma Γ— (1 / log(networkSize))

Concept: Larger networks have more parameters to tune, so individual parameter changes should be more conservative to avoid disrupting complex learned structures. The logarithmic scaling provides smooth adaptation across network sizes.

This adaptive approach balances exploration and exploitation: smaller networks can explore broadly, while larger networks receive more refined adjustments.

Note

Effective for problems where network size evolves during optimization

Parameters:
  • probability – Probability (in [0.0, 1.0]) that each boundary will be mutated

  • sigma – Base standard deviation (will be scaled down based on network size)

  • justUsedNodes – If true, only applies mutation to judgment nodes that were used during traversal (node.used == true).

inline void callBoundaryMutationEdgeSizeDependingSigma(const float probability, const float sigma, bool justUsedNodes)#

Applies normal boundary mutation with sigma adapted to number of node edges.

This method implements adaptive boundary mutation where the mutation strength decreases as the number of outgoing edges increases. The adaptive sigma is calculated as:

sigmaNew = sigma Γ— (1 / log(edgeCount))

Concept: Judgment nodes with more outgoing edges partition the feature space into more intervals, creating finer-grained decision boundaries. These require more careful adjustment to avoid disrupting the detailed partitioning structure.

This node-level adaptation is more fine-grained than network-level adaptation, allowing heterogeneous mutation strengths within the same network (each node hase his own distribution).

Note

Particularly useful when networks have heterogeneous judgment node structures

Parameters:
  • probability – Probability (in [0.0, 1.0]) that each boundary will be mutated

  • sigma – Standard deviation (will be scaled down based on edge count)

  • justUsedNodes – If true, only applies mutation to judgment nodes that were used during traversal (node.used == true).

inline void callBoundaryMutationFractal(const float probability, std::vector<float> minF, std::vector<float> maxF, bool justUsedNodes)#

Applies fractal boundary mutation to all judgment nodes with fractal structure.

This specialized mutation operator is designed for judgment nodes that use fractal-based edge patterns. Instead of directly mutating boundaries, it mutates the underlying production rule parameters that generate the fractal structure, then recalculates all boundaries accordingly.

For each judgment node in each non-elite individual:

  1. Mutates production rule parameters uniformly within valid ranges

  2. Recalculates fractal lengths based on k_d parameters

  3. Regenerates all boundaries to match the new fractal pattern

Warning

Only applicable if fractalJudgment is enabled (fractalJudgment = True)

Parameters:
  • probability – Probability (in [0.0, 1.0]) that each production parameter will be mutated

  • minF – Vector of minimum values for all features (used for boundary recalculation)

  • maxF – Vector of maximum values for all features (used for boundary recalculation)

  • justUsedNodes – If true, only applies mutation to judgment nodes that were used during traversal (node.used == true).

inline void crossover(float propability = 1, std::string type = "", bool traversalNeighbor = false, float lowerBoundTraversalCounter = 0.9, float upperBoundTraversalCounter = 1.1)#

Performs crossover (recombination) between pairs of individuals in the population.

This method implements crossover, a genetic operator that exchanges parts of the gene structure between parent networks to create offspring. The process:

Pairing:

  1. Shuffles all individual indices randomly

  2. Pairs adjacent individuals in the shuffled order (0-1, 2-3, 4-5, etc.)

  3. Skips pairs where either parent is elite (elite protection)

Node exchange:

  1. Determines maximum exchangeable nodes: min(parent1.size, parent2.size). This is only needed if parents have different network sizes caused by applying callAddDelNodes().

  2. For each node (up to max exchangeable nodes) given type: β€œuniform”:

    • With passed probability: swaps nodes at that position

    • After swap: repairs any invalid edges (edges pointing to non-existent nodes) β€œonepoint”: draw a random number from the genotype and exchange all nodes until this point β€œrandomWidth”: exchanges subnetworks of potentially different widths between parents:

    1. Identifies successor nodes (active subnetwork) in both parents using findSuccessorNodes()

    2. Creates swap maps (old index -> new index) for remapping nodes between parents

    3. Validates that exchanging subnetworks won’t reduce networks below 2 inner nodes (invalid network)

    4. Swaps nodes up to min(successor1.size, successor2.size) between the subnetworks

    5. Handles overhang nodes (extra nodes in larger subnetwork):

      • Adds overhang nodes from larger to smaller parent (addOverhangNodes)

      • Remaps all node IDs and edges in both parents to maintain consistency

      • Deletes overhang nodes from the larger parent (deleteOverhangNodes)

    6. This allows crossover between networks of different effective widths while preserving structure and modularity.

Edge repair rules:

  • For β€œuniform” and β€œonepoint”: Only check edges for the smaller parent (edges may become invalid after receiving nodes)

  • For β€œrandomWidth”: Check edges for the larger parent (due to node deletion creating potential dangling edges)

  • changeFalseEdges() redirects any dangling edges to valid random nodes

  • Prevents graph structure corruption after recombination

Note

tournamentSelection() must have been called to set indicesElite

Note

Only nodes up to min(size1, size2) can be exchanged for β€œuniform” and β€œonepoint” due to position-based matching

Note

For β€œrandomWidth”, parent networks must have more than 2 inner nodes to safely use changeFalseEdges()

Parameters:
  • propability – Probability (in [0.0, 1.0]) that each node position will be exchanged (used for β€œuniform” type only). Default is 1.

  • type – Type of the crossover:

    • ”uniform”: selects each node and exchanges them with given probability

    • ”onepoint”: draws a random cutpoint from the genotype and exchanges all nodes until this point

    • ”randomWidth”: exchanges subnetworks of different widths where all succesor nodes of a randomly selected node are exchanged

  • traversalNeighbor – If true, the crossover is only applied to pairs of individuals that are neighbors in the traversal space calculated by traverseCounter.

  • lowerBoundTraversalCounter – Lower bound for traverseCounter ratio to consider individuals as neighbors (used if traversalNeighbor is true)

  • upperBoundTraversalCounter – Upper bound for traverseCounter ratio to consider individuals as neighbors (used if traversalNeighbor is true)

inline std::unordered_map<int, int> initNodeSwapMap(const std::vector<int> &subnodes1, const std::vector<int> &subnodes2, int sizeNetwork2)#

Initializes a node index mapping between two subnode vectors for crossover operations.

Creates a mapping from subnodes1 indices to subnodes2 indices. If subnodes1 is longer, the excess nodes are mapped to new indices starting from sizeNetwork2. This ensures proper node index translation when combining networks during genetic crossover.

Parameters:
  • subnodes1 – Vector of node indices from the first network

  • subnodes2 – Vector of node indices from the second network

  • sizeNetwork2 – The current size of the second network, used as base for new indices

Returns:

std::unordered_map<int, int> Mapping from subnodes1 indices to corresponding target indices

inline void addOverhangNodes(const std::vector<int> &successor1, const std::vector<int> &successor2, Network &parent1, Network &parent2, bool copy = false)#

Adds overhang nodes from the larger parent subnetwork to the smaller parent subnetwork during crossover.

When two parents have different numbers of successor nodes, this method transfers the excess nodes (overhang) from the larger parent’s network to the smaller parent’s network. The overhang nodes are moved from parent1 to parent2’s innerNodes vector.

Note

The added node IDs and edges may need further adjustment to maintain graph validity.

Warning

successor1 and successor2 must be sorted in ascending order

Parameters:
  • successor1 – Vector of successor node indices from the larger parent subnetwork

  • successor2 – Vector of successor node indices from the smaller parent subnetwork

  • parent1 – The parent network from which overhang nodes are extracted

  • parent2 – The parent network to which overhang nodes are added

inline void deleteOverhangNodes(const std::vector<int> &successor1, const std::vector<int> &successor2, Network &parent1)#

Deletes overhang nodes from the larger parent subnetwork.

Removes excess nodes from parent1 when it has more successor subnodes than parent2. For each overhang node to be deleted, this method:

  1. Creates a deletion map that remaps indices of nodes after the deleted node (shifting them down by one)

  2. Assigns the deleted node index a random valid edge

  3. Remaps all node IDs and edges in the network to maintain consistency after deletion

  4. Erases the node from the innerNodes vector This process ensures that all references remain valid after node removal, preventing dangling references.

Note

The parent1 network must have more than 2 inner nodes, otherwise changeEdge() will cause an error when trying to assign a random valid edge for the deleted node.

Warning

successor1 and successor2 must be sorted in ascending order

Parameters:
  • successor1 – Vector of successor node indices from the larger parent subnetwork

  • successor2 – Vector of successor node indices from the smaller parent subnetwork

  • parent1 – The larger parent subnetwork from which overhang nodes will be deleted (modified in-place)

inline std::vector<int> findSuccessorNodes(auto &individual, int subNodesStart = -1, int nSelectedNodes = -1, bool traversalNeighbor = false)#

Find all successor nodes reachable after path traversal from a given start node.

Starting from subNodesStart, this method collects successor nodes by comparing traverse counters. Nodes whose traverse counter is greater than that of the start node are considered successors. If the start node is unused, only the start node itself is returned. The resulting indices are sorted in ascending order.

Parameters:
  • individual – The individual (network) whose inner nodes are traversed.

  • subNodesStart – Index of the starting node in individual.innerNodes. If -1, a random index is selected uniformly from the available inner nodes.

  • nSelectedNodes – Maximum number of successor nodes to collect (excluding the start node). If -1, a random count between 1 and the number of inner nodes minus one is chosen.

  • traversalNeighbor – If true, collects nodes with traverse counters within Β±10% of the start node’s counter, instead of strictly greater.

Returns:

A sorted vector of node indices comprising the start node and up to nSelectedNodes successors.

inline void callAddDelNodes(std::vector<float> &minF, std::vector<float> &maxF, float junk = 0, bool noElite = false)#

Applies node addition and deletion to individuals in the population.

This method allowing the network topology to grow and shrink during evolution. For each individual, the addDelNodes() method decides whether to add or delete a node.

Addition:

  • Adds either a judgment node or processing node (based on pnf/(pnf+jnf) ratio)

  • Restriction: Only adds a new node if all current nodes are traversed during the transition path (the nodes flag β€œused” = true).

Deletion:

  • Removes one unused node

  • Updates all node IDs and edge connections to maintain graph validity Restriction: only delete a node if the node is not traversed during the transition path (the node flag β€œused” = false).

  • Enables network pruning to reduce complexity

This operator allows GNP to automatically discover appropriate network sizes, and evolving toward optimal complexity for the problem. This extansion of GNP is called variable-size Genetic Network Programming.

Note

See also our proposed operator in: β€œVariable-Size Genetic Network Programming for Portfolio Optimization with Trading Rules” by Fabian KΓΆhnke & Christian Borgelt, EvoApplications 2025 https://doi.org/10.1007/978-3-031-90062-4_18

Note

This operator has not influence on the individuals fitness

Parameters:
  • minF – Vector of minimum values for all features (for new judgment node initialization)

  • maxF – Vector of maximum values for all features (for new judgment node initialization)

  • junk – ratio of protected unused nodes (junk DNA). A value of 0.1 protects 10% of unused nodes and at least one node is always protected.

  • noElite – If true, elite individuals are protected normaly this is not necessary because the operator addDelNodes() is fitness neutral. But because of the fitness neutraly is given by used nodes it just protects node of the last traversal path. If you use multiple traversal path per generation, e.g. using multiple seeds for evaluation a elite protection is not garanteed and noElite should be set to true. Default is false.

inline void gymnasiumMultiSeed(GymEnvWrapper &env, int dMax, int maxSteps, int maxConsecutiveP, int worstFitness, const std::vector<int> &seeds)#

Evaluates all individuals across multiple seeds and stores per-seed rewards.

Runs fitGymnasium() for each individual on each seed. The per-seed rewards are stored in network.fitnessValues. The aggregated fitness is stored in network.fitness as the mean reward across all seeds.

Parameters:
  • env – GymEnvWrapper object providing interface to the Gymnasium environment

  • dMax – Maximum consecutive judgment nodes per decision

  • maxSteps – Maximum episode length

  • maxConsecutiveP – Maximum consecutive processing nodes allowed

  • worstFitness – Fitness value assigned when networks violate constraints

  • seeds – Vector of random seeds for environment initialization

inline void calculateParetoObjectives(float landingThreshold = 100.0f)#

Calculates Pareto objectives (landing rate, mean reward) from fitnessValues.

For each individual, extracts two objectives from the per-seed rewards stored in fitnessValues:

  • objectives[0] = landing rate (fraction of seeds with reward > landingThreshold)

  • objectives[1] = mean reward across all seeds

The scalar fitness is set as: landingRate * 1000 + meanReward This two-stage fitness ensures landing rate has priority while mean reward breaks ties.

Parameters:

landingThreshold – Reward threshold above which a run counts as a landing (default: 100)

Pre:

gymnasiumMultiSeed() must have been called to populate fitnessValues

inline void paretoTournamentSelection(int N, int E_reward, int E_landing)#

Performs Pareto-based tournament selection with dual elitism.

Extends paretoTournamentSelection with two elite groups:

  • E_reward best individuals by mean reward (exploitation)

  • E_landing best individuals by landing rate (exploration) This ensures that landing behavior is never lost through mutation.

Parameters:
  • N – Tournament size

  • E_reward – Number of elite individuals by mean reward

  • E_landing – Number of elite individuals by landing rate

inline void setEliteDual(int E_reward, int E_landing, const std::vector<Network> &individuals, std::vector<Network> &selection)#

Identifies elite individuals by two criteria: mean reward AND landing rate.

Parameters:
  • E_reward – Number of elite by best fitness (mean reward)

  • E_landing – Number of elite by best landing rate

  • individuals – Copy of current population

  • selection – Reference to new population being constructed

static inline bool dominates(const std::vector<float> &a, const std::vector<float> &b)#

Checks if objectives A dominate objectives B (Pareto dominance).

A dominates B if A is >= B in ALL objectives AND strictly > in at least one.

Parameters:
  • a – First objective vector

  • b – Second objective vector

Returns:

true if a dominates b

Network#

class Network#

Graph-based control structure for Genetic Network Programming (GNP).

The Network class represents the directed graph architecture used in Genetic Programming (GNP), an evolutionary computation method based on graph structures rather than trees. A GNP network consists of:

  • Judgment nodes – evaluate conditional expressions based on input features

  • Processing nodes – perform actions or output decisions

  • Directed edges – define execution flow through the network

Unlike traditional Genetic Programming (GP), GNP allows node reuse through recurrent graph connections, enabling compact models with memory effects and efficient decision-making. This makes GNP suitable for real-time control, adaptive agents, reinforcement learning, and optimization problems.

The network evolves over generations through evolutionary operators such as mutation and crossover, gradually improving its performance based on a fitness function.

Note

In the context of this implementation, the Network acts as the central decision structure that interacts with input data while evolving its topology. Furthermore, the networks can grow and shrink during the evolution using the method addDelNodes().

Note

See also: β€œVariable-Size Genetic Network Programming for Portfolio Optimization

with Trading Rules” by Fabian KΓΆhnke & Christian Borgelt, EvoApplications 2025

https://doi.org/10.1007/978-3-031-90062-4_18

Constructor

inline Network(std::shared_ptr<std::mt19937_64> _generator, unsigned int _jn, unsigned int _jnf, unsigned int _pn, unsigned int _pnf, bool _fractalJudgment, std::vector<int> _nFeatureValues = {})#

Constructs a Network with specified parameters and initializes all nodes.

This constructor creates a complete GNP network by initializing:

  • A start node that serves as the entry point for network execution

  • A specified number of judgment nodes with random function assignments

  • A specified number of processing nodes with random function assignments

  • Edges between nodes according to the specified pattern (standard or fractal)

For judgment nodes with fractal patterns enabled, the constructor calculates the number of outgoing edges according to random_k_d_combination().

Parameters:
  • _generator – Shared pointer to a Mersenne Twister random number generator (std::mt19937_64) used for all stochastic operations

  • _jn – Number of initial judgment nodes in the network

  • _jnf – Number of judgment node function types available (determines random function assignment range)

  • _pn – Number of initial processing nodes in the network

  • _pnf – Number of processing node function types available (determines random function assignment range)

  • _fractalJudgment – If true, judgment nodes use fractal-based edge patterns; if false, standard edge patterns are used

  • _nFeatureValues – set the number of features values to distinguish between numerical and categorical data

    • for numerical features: set 0 at the i-th feature

    • for categorical features: set the numbers of categories at feature position i. This will be the amount of outgoing edges of a judgment node

    • default is an empty vector and all features are treated as numerical

Member Functions

inline void clearUsedNodes()#

Resets the usage status of all nodes in the network.

This method iterates through all inner nodes and sets their β€˜used’ flag to false. This is typically called before a new traversal of the network to ensure accurate tracking of which nodes are visited during execution. The usage information is important for:

inline void countUsedNodes()#

Counts the number of nodes that have been marked as used and stores the result.

This method iterates through all inner nodes (judgment and processing nodes) and counts how many have their β€˜used’ flag set to true. The result is stored in the member variable nUsedNodes. This information is crucial for:

  • Determining network efficiency (ratio of used to total nodes)

  • Making decisions about node addition/deletion during evolution (addDelNodes())

Note

This function should be called after a network traversal to get accurate usage statistics.

inline void traversePath(const std::vector<std::vector<float>> &X, int dMax)#

Traverses the network for a complete dataset and records all decisions.

This inline function executes the network for each row in the feature matrix X. For each input vector, it:

  1. Clears the decisions vector and resets node usage flags (see clearUsedNodes())

  2. Initializes traversal at the start node’s first edge

  3. Processes each judgment node (corresponding to a feature) and processing node through the network traversal

  4. Records the decision made by each traversed processing node

Note

The decisions vector will contain one integer decision per row in X

Note

If dMax is exceeded during any decision, the invalid flag is set to true and the individual should be penalized

Parameters:
  • X – Feature matrix where each inner vector represents one sample with multiple features

  • dMax – Maximum number of consecutive judgment nodes allowed before a decision must be made (prevents infinite loops)

template<typename dataContainer>
inline int decisionAndNextNode(const dataContainer &data, int dMax)#

Makes a single decision based on input data and transitions to the next node.

This inline template function is the core decision-making mechanism of the network. It processes a single data sample and navigates through the network graph until reaching a processing node, which provides the decision. The execution flow:

  1. If current node is a Processing Node (P):

    • Returns the node’s function value as the decision

    • Sets the next node via the node’s outgoing and stores them in member currentNodeID

    • Increments the consecutive processing node counter

  2. If current node is a Judgment Node (J):

    • Resets the consecutive processing node counter

    • Enters a loop traversing judgment nodes:

      • Evaluates the judgment condition using the specified feature. The feature is specified by the node member f (function)

      • Follows the appropriate edge based on the judgment result (see judge())

      • Increments the judgment depth counter

      • If dMax is exceeded, marks the network as invalid and returns error code

    • Once a processing node is reached, returns its function value as decision

The function uses template parameters to accept various container types (std::vector, std::array, etc.) for the input data, providing flexibility in usage.

Note

Only works correctly for one-dimensional data access patterns (single data sample)

Warning

Exceeding dMax sets the invalid flag to true and returns an error value template for passing std::vector, std::array …

Template Parameters:

dataContainer – Type of the data container (must support operator[] for indexing)

Parameters:
  • data – Input feature vector for the current sample (indexed by node function f)

  • dMax – Maximum of consecutive judgment nodes before forcing termination

Returns:

Integer decision value of the reached processing node (member f of processing node), or std::numeric_limits<int>::lowest() if the network becomes invalid

inline void initPathTraversal(double startingFitness = 0)#

Initializes the network state for a new path traversal.

Prepares the network for sequential decision-making by:

  1. Clearing all node usage flags

  2. Resetting traverse counters for all nodes and the network

  3. Setting the current node to the start node’s target

  4. Resetting fitness, validity, and consecutive processing node counters

After calling this method, the network is ready to receive observations via decisionAndNextNode() one step at a time.

Parameters:

startingFitness – Optional initial fitness value to set before traversal (default is 0)

inline void fitGymnasium(GymEnvWrapper &env, int dMax, int maxSteps, int maxConsecutiveP, int worstFitness, int seed, bool newRun = true)#

Evaluates network fitness using an OpenAI Gymnasium-compatible environment.

This method executes the network as a reinforcement learning agent in a Gymnasium environment.

See also : https://gymnasium.farama.org

The evaluation process simulates a complete episode:

  1. Initialization:

    • Resets the environment and obtains initial observation

    • Clears node usage tracking and resets network state

    • Initializes fitness accumulator and counters

  2. Episode Loop (until termination):

    • Network makes decision based on current observation

    • Checks validity constraints (see parameters dMax and maxConsecutiveP)

    • If constraints violated, assigns worst fitness and terminates

    • Executes action in environment via step() function

    • Accumulates reward into fitness

    • Updates observation for next iteration

    • Checks termination conditions (done flag or max steps reached)

  3. Termination Conditions:

    • Environment signals episode completion (done flag)

    • Maximum step limit reached

    • Network becomes invalid (exceeds dMax)

    • Too many consecutive processing nodes (exceeds maxConsecutiveP)

Warning

The network must produce valid actions for the specific Gymnasium environment

Parameters:
  • env – GymEnvWrapper object providing the Gymnasium environment interface

  • dMax – Maximum consecutive judgment nodes per decision (prevents infinite loops)

  • maxSteps – Maximum number of environment steps per episode

  • maxConsecutiveP – Maximum consecutive processing nodes allowed. Here we can control the number of possible actions after using the observation data again.

  • worstFitness – Fitness value assigned when network violates constraints

  • seed – Random seed for environment initialization

  • gamma – discount factor of the rewards

  • newRun – If true, resets network state for a new episode; if false, continues from current state (useful for multi-episode evaluation)

inline void fitCartpole(int dMax, int penalty, int maxSteps, int maxConsecutiveP)#

Evaluates network fitness on the CartPole balancing problem.

This mthod implements a specialized fitness evaluation for the classic CartPole control problem (similar to OpenAI Gymnasium’s CartPole-v1). The CartPole task requires balancing a pole on a moving cart through discrete left/right actions.

See also: https://gymnasium.farama.org/environments/classic_control/cart_pole/

The evaluation process:

  1. Initialization:

    • Creates a new CartPole environment instance with the network’s random generator

    • Resets network state and obtains initial observation (cart position, velocity, pole angle, angular velocity)

    • Initializes fitness counter, which increments for each successful step

  2. Episode Loop:

    • Increments fitness for each successful timestep

    • Executes current decision in CartPole environment

    • Obtains new observation from environment

    • Network makes next decision based on new observation

    • Checks termination conditions

  3. Termination Conditions:

    • Pole falls beyond recovery angle (environment sets terminated flag)

    • Maximum steps reached (episode length limit)

    • Network constraint violations:

      • Invalid flag set (judgment dMax exceeded)

      • Too many consecutive processing nodes (maxConsecutiveP exceeded)

      • Constraint violations apply penalty: fitness is divided by penalty factor

  4. Fitness Calculation:

    • Base fitness: number of steps the pole remained balanced

    • Penalized fitness: base fitness / penalty (if constraints violated)

    • Optimal fitness: maxSteps (indicates perfect balancing for entire episode)

Parameters:
  • dMax – Maximum consecutive judgment nodes per decision (prevents infinite loops in graph traversal)

  • penalty – Divisor applied to fitness when network violates structural constraints

  • maxSteps – Maximum episode length (prevents indefinite episodes and caps maximum fitness)

  • maxConsecutiveP – Maximum consecutive processing nodes allowed Here we can control the number of possible actions after using the observation data again.

inline void checkNodeIndicesAndEdges(std::string msg = "")#

Validates node indices and edge connections in the network.

This method performs integrity checks on the network’s structure by:

  1. Verifying that each node’s ID matches its index in the innerNodes vector

  2. Ensuring that all edges point to valid node indices within the bounds of innerNodes

If any inconsistencies are found (e.g., node ID mismatch or edge pointing to non-existent node), error messages are printed to standard error output.

inline void changeFalseEdges()#

Corrects invalid edge connections that point to non-existent nodes.

This method validates and repairs the network’s edge structure by ensuring all edges point to valid node indices. This is necessary after structural mutations such as node deletion (from addDelNodes()), where edges may become invalid. The repair process:

  1. Iterates through all nodes in the network

  2. For each node, examines all outgoing edges

  3. If an edge index exceeds the valid range (β‰₯ innerNodes.size()), it indicates the edge points to a deleted or non-existent node

  4. If an edge points to the node itself (edge == node.id), creating a self-loop

  5. Calls the node’s changeEdge() method to randomly select a new valid target node

This ensures the network graph remains well-defined with all edges pointing to existing nodes and preventing self-loops, which helps avoid runtime errors during network traversal.

Note

This function should be called after any operation that removes nodes

inline void remapNodeIdsAndEdges(std::unordered_map<int, int> &map, const std::vector<int> &nodeIndices, bool includeStartNode = false)#

Remaps node IDs and their associated edges using a provided mapping.

This method updates node IDs and edge references for a subset of nodes specified by their indices. For each node in the given indices, if its ID exists in the mapping, it is replaced with the mapped value. Similarly, all edges of these nodes are updated if they exist in the mapping. This is useful for renumbering or consolidating node identifiers while maintaining graph connectivity.

Parameters:
  • map – A reference to an unordered map where keys are old node IDs and values are new node IDs to remap to.

  • nodeIndices – A vector of indices specifying which nodes in the innerNodes collection should be processed for remapping.

  • includeStartNode – If true, remaps the first edge of the start node if it exists in the mapping. Defaults to false.

inline void addDelNodes(std::vector<float> &minF, std::vector<float> &maxF, float junk, std::vector<int> &nFeatureValues)#

Performs network grow and shrink during the evolution.

This method implements structural mutation by adding or removing nodes from the network. See also our proposed operator in: β€œVariable-Size Genetic Network Programming for Portfolio Optimization with Trading Rules” by Fabian KΓΆhnke & Christian Borgelt, EvoApplications 2025 https://doi.org/10.1007/978-3-031-90062-4_18

The mutation process:

Decision Phase:

  • 50% probability to add a node vs. delete a node

  • Determines node type (processing vs. judgment) based on pnf/(pnf+jnf) ratio

Addition Branch (if resultAdd == true):

  • Condition: all nodes are currently used (nUsedNodes β‰₯ innerNodes.size() Γ— 1)

  • Processing Node Addition:

    • Assigns random function from [0, pnf-1]

    • Creates outgoing edge to random node in network

    • Increments pn counter

  • Judgment Node Addition:

    • Assigns random function (feature index) from [0, jnf-1]

    • Sets edge structure (standard or fractal based on fractalJudgment flag)

    • For fractal mode: generates k-d combination, production rule parameters, fractal lengths

    • Sets edge boundaries based on feature value ranges [minF, maxF]

    • Increments jn counter

  • Only one node added per call (break statement)

Deletion Branch (if resultAdd == false):

  • Conditions:

    • More than one unused node exists (innerNodes.size() - nUsedNodes > 1)

    • Current node is unused (innerNodes[n].used == false)

  • Deletion Process:

    1. Updates all node IDs greater than deleted node’s ID (decrement by 1)

    2. Updates all edges pointing to nodes after deleted node (decrement by 1)

    3. Redirects edges pointing directly to deleted node using changeEdge()

    4. Updates start node edge if necessary

    5. Decrements jn or pn counter based on deleted node type

    6. Removes node from innerNodes vector

  • Only one node deleted per call (break statement)

Warning

This method must be called bevore edgeMutation()! Reason: if edges are change by edgeMutation(), the node flag β€œused” is not guaranteed to be correct.

Parameters:
  • minF – Vector of minimum feature values for each feature dimension (used for judgment node boundary initialization)

  • maxF – Vector of maximum feature values for each feature dimension (used for judgment node boundary initialization) @junk ratio of protected unused nodes (junk DNA). A value of 0.1 protects 10% of unused nodes.

Post:

All edges remain valid (no dangling edges)

Post:

Node IDs are contiguous from 0 to innerNodes.size()-1

inline int countEdges(bool justUsedNodes = false)#

Counts the total number of edges in the network, optionally filtering by used nodes.

This method iterates through all inner nodes and sums the number of outgoing edges. If the justUsedNodes parameter is set to true, only nodes that are currently marked as used (used == true) are included in the count.

Parameters:

justUsedNodes – If true, only counts edges from nodes that are currently used. If false, counts edges from all nodes regardless of usage status.

Returns:

The total count of edges in the network, filtered by usage if specified.

inline float pnRatio()#

Calculates the ratio of processing nodes to total nodes in the network.

This method iterates through all inner nodes and counts the number of processing nodes (pn) and judgment nodes (jn).

Returns:

The ratio of processing nodes to total nodes, calculated as pn / (pn + jn). If there are no nodes, returns 0 to avoid division by zero.

Node#

class Node#

This class defines the node of the GNP graph.

A Node represents a fundamental building block in the Genetic Network Programming (GNP) graph structure. Each node can be one of three types:

  • Start Node (S) - The entry point for network execution

  • Processing Node (P) - Executes actions and produces output decisions

  • Judgment Node (J) - Evaluates conditional branches based on input features

Nodes are connected through directed edges that define the graph traversal through the network. Judgment nodes use boundary-based decision rules to select which outgoing edge to follow based on feature values.

Constructor

inline Node(std::shared_ptr<std::mt19937_64> _generator, unsigned int _id, std::string _type, unsigned int _f)#

Constructs a Node with specified parameters.

Creates a new node in the GNP network with the given identifier, type, and function. The node is initialized without edges or boundaries, which must be set separately using setEdges() and setEdgesBoundaries() methods.

Parameters:
  • _generator – Shared pointer to a Mersenne Twister random number generator (std::mt19937_64) for stochastic operations

  • _id – Unique identifier for this node within the network. The id should be always the index of β€œinnerNodes” in a Network.

  • _type – Node type as string:

    • ”S” for Start Node,

    • ”P” for Processing Node,

    • ”J” for Judgment Node

  • _f – Node function: for judgment nodes this is the feature index to evaluate; for processing nodes this is the output/action value

Member Functions

inline void setEdges(std::string type, int nn, int k = 0)#

Initializes the outgoing edges of the node based on its type and network size.

This method creates the edge structure for different node types according to GNP rules:

Judgment Nodes (type β€œJ”):

  • Can have multiple outgoing edges (between 2 and nn-1), where nn is the number of processing and judgment nodes of a Network

  • Randomly shuffles candidates

    • If parameter k=0: randomly selects between 2 and nn-1 edges

    • If parameter k>0: selects exactly k edges

  • Edges represent conditional branches based on feature value intervals

Processing Nodes (type β€œP”) and Start Nodes (type β€œS”):

  • Have exactly one outgoing edge

  • Randomly selects a successor node (excluding self to prevent self-loops)

The edge selection ensures well-defined graph structure with no self-loops for different node types.

Note

Self-loop are always prevented

Parameters:
  • type – Node type string: β€œJ” (Judgment), β€œP” (Processing), or β€œS” (Start)

  • nn – Total number of nodes in the network (used to determine valid edge targets)

  • k – Number of outgoing edges for judgment nodes (0 = random selection between 2 and nn-1, >0 = fixed number)

inline int judge(float v)#

Evaluates a feature value and determines which outgoing edge to follow.

This method implements the decision logic for judgment nodes by mapping a continuous feature value to a discrete edge index. The feature space is divided into intervals defined by the boundaries vector, and this function determines which interval contains the given value.

Algorithm:

  1. Boundary cases:

    • If v ≀ first boundary: return edge 0 (leftmost interval)

    • If v β‰₯ last boundary: return last edge index (rightmost interval)

  2. Binary search (for inner values):

    • Efficiently locates the interval [boundaries[i], boundaries[i+1]) given v

    • Returns the index i such that boundaries[i] ≀ v < boundaries[i+1]

The boundaries divide the feature space into non-overlapping intervals, each corresponding to one outgoing edge. This enables GNP judgment nodes to make multi-way decisions based on continuous feature values.

Note

This function uses binary search for efficient interval lookup

Note

Return value -1 indicates an algorithmic error (should not occur with valid boundaries)

Warning

Boundaries must be in ascending order: boundaries[i] < boundaries[i+1]

Parameters:

v – Feature value for evaluation (continuous real number)

Returns:

Index of the edge to follow (integer in range [0, edges.size()-1]), or -1 if an error occurs

inline void setEdgesBoundaries(float minf, float maxf, std::vector<float> lengths = {})#

Sets the decision boundaries that partition the feature space for judgment node.

This method creates the boundary values that divide the continuous feature space into intervals, one for each outgoing edge. Each interval corresponds to one possible judgment outcome (edge selection). Therfore, the number of boundaries is equal to the number of edges of a judgment node.

Two modes of operation:

  1. Uniform spacing (lengths vector empty):

    • Divides the range [minf, maxf] into edges.size() equal intervals

    • Each interval has width: (maxf - minf) / edges.size()

  2. Custom spacing (lengths vector provided):

    • Uses relative lengths from the lengths vector

    • Each interval i has width: (maxf - minf) Γ— lengths[i]

    • Enables fractal or non-uniform partitioning patterns

    • The values in member lengths should sum to 1.0 for proper coverage

The resulting boundaries vector contains edges.size()+1 values that define the edges.size() intervals: [b[0], b[1]), [b[1], b[2]), …, [b[n-1], b[n]]

Note

If lengths is provided, it should have size edges.size() with values summing to 1.0

Note

Uniform spacing is used when lengths is empty or has size 0

Parameters:
  • minf – Minimum feature value (lower boundary of the feature space)

  • maxf – Maximum feature value (upper boundary of the feature space)

  • lengths – optional vector of relative interval lengths (each in range [0,1], should sum to 1.0)

inline void edgeMutation(float propability, int nn, int k, int N)#

Stochastically mutates the outgoing edges of the node.

This method implements edge mutation, a key evolutionary operator in GNP that modifies the network topology. For each outgoing edge, the function:

  1. Draws a random boolean from a Bernoulli distribution with given probability

  2. If true, replaces the current edge target with a new random valid node

  3. Uses changeEdge() to ensure the new target is valid (no self-loops, different from current)

Edge mutation allows the network structure to evolve by redirecting connections, potentially discovering better execution paths through the graph. This operation preserves the number of edges while changing their targets but multiple outgoing edges can have the same node as an successor.

Expected number of mutated edges: probability Γ— edges.size()

Note

No self-loops are introduced by the mutation and the edges vector maintains its original size

Parameters:
  • propability – Probability (in range [0.0, 1.0]) that each individual edge will be mutated

  • nn – Total number of nodes in the network (used to determine valid mutation targets)

  • adaptToEdgeSize – If true, mutation probability is adapted to the number of edges (e.g., propability / edges.size()). If false, the provided propability is used directly for each edge.

inline int changeEdge(int nn, int &edge)#

Selects a new random target node for an edge while avoiding self-loops and duplicates.

This method generates a new valid successor node for an edge by randomly sampling from the set of all nodes until finding one that satisfies the constraints:

Constraints:

  • Must not equal this->id (prevents self-loops)

  • Must not equal the current edge value (ensures actual change)

  • Must be in valid range [0, nn-1]

The method uses rejection sampling: it draws random integers until finding one that meets the constraints.

Warning

Requirements: nn > 2 (at least 3 nodes required to ensure a valid alternative exists because of the constraints). Otherwise method could run indefinitely.

Parameters:
  • nn – Total number of nodes in the network (defines the valid range [0, nn-1])

  • edge – Current edge value (by reference, though not modified by this function)

Returns:

New valid node index for the edge

inline void boundaryMutationUniform(float propability)#

Mutates decision boundaries by shifting them within their adjacent intervals using uniform distribution.

This function implements boundary mutation for judgment nodes, allowing the decision thresholds to evolve without changing the network topology. For each interior boundary:

Mutation process:

  1. Draws a Bernoulli random variable with given probability

  2. If true, samples a new boundary value uniformly from [boundaries[i-1], boundaries[i+1]]

  3. Replaces the old boundary with the new value

Key properties:

  • Only inner boundaries are mutated (indices 1 to boundaries.size()-2)

  • First and last boundaries remain fixed (preserve feature space range)

  • Preserves monotonicity: boundaries[i-1] < boundaries[i] < boundaries[i+1]

This uniform sampling approach provides unbiased exploration of the boundary space, allowing both small and large shifts with equal probability within the valid range.

Parameters:

propability – Probability (in range [0.0, 1.0]) that boundary will be mutated

inline void boundaryMutationFractal(float propability, const std::vector<float> &minf, const std::vector<float> &maxf)#

Mutates boundaries by adjusting fractal production rule parameters and recalculating the fractal structure according to a L-System.

This specialized mutation operator is used for judgment nodes with fractal-based edge patterns. Instead of directly mutating boundaries, it mutates the underlying production rule parameters that generate the fractal structure (see fractalLengths()), then recomputes the boundaries accordingly.

Mutation process:

  1. For each inner production rule parameter (indices 1 to size-2):

    • Draws a Bernoulli random variable with given probability

    • If true, uniformly samples new value between adjacent parameters

    • Ensures parameters remain in [0, 1] and properly ordered

  2. After mutating any parameter:

Key properties:

  • Maintains the fractal structure defined by k_d parameters (k base, d depth)

  • Production rule parameters remain ordered: 0 = p[0] < p[1] < … < p[n-1] = 1

  • Boundaries are recalculated to match the feature range [minf[f], maxf[f]]

Note

productionRuleParameter must be initialized with values in [0, 1]

Parameters:
  • propability – Probability (in range [0.0, 1.0]) that each production rule parameter will be mutated

  • minf – Vector of minimum values for all features (indexed by this->f)

  • maxf – Vector of maximum values for all features (indexed by this->f)

inline void boundaryMutationNormal(float propability, float sigma)#

Mutates decision boundaries by shifting them using a normal (Gaussian) distribution.

This method implements boundary mutation with a normal distribution centered at the current boundary value. For each inner boundary:

Mutation process:

  1. Draws a Bernoulli random variable with given probability

  2. If true:

    • Centers a normal distribution at the current boundary (ΞΌ = boundaries[i])

    • Samples a new value from N(ΞΌ, σ²)

    • Accepts the new value only if it falls within [boundaries[i-1], boundaries[i+1]]

    • Rejects values that would cause boundary crossing or reordering

Key properties:

  • Only inner boundaries are mutated (first and last remain fixed)

  • Small shifts are more likely than large shifts (Gaussian distribution)

  • Preserves strict ordering: boundaries[i-1] < boundaries[i] < boundaries[i+1]

Comparison to uniform mutation:

  • Uniform: unbiased exploration, all valid positions equally likely

  • Normal: biased toward small changes, enables fine-tuning

  • Normal: better for local optimization, worse for global exploration

The sigma parameter controls mutation strength: small sigma β†’ fine-tuning, large sigma β†’ larger jumps (but still biased toward center).

Parameters:
  • propability – Probability (in range [0.0, 1.0]) that each interior boundary will be mutated

  • sigma – standard deviation of the normal distribution (later scaled by mu)

Fractal#

Utility functions for fractal-based edge pattern generation in GNP judgment nodes.

This file provides utilities for creating hierarchical, self-similar decision structures in judgment nodes. The fractal approach enables:

  • Hierarchical partitioning: Feature space divided using L-system-inspired recursive patterns

  • Evolutionary optimization: Production rule parameters can evolve to discover effective patterns

The fractal pattern generation follows the process:

  1. Determine k (branching factor) and d (depth) such that k^d equals number of edges

  2. Generate production rule parameters (random cut points in [0,1])

  3. Calculate relative lengths through recursive application of the production rule

  4. Map these lengths to actual feature space boundaries

This approach is inspired by L-systems and provides a biologically-motivated alternative to uniform or arbitrary boundary placement.

Functions

inline std::pair<int, int> random_k_d_combination(int N, std::shared_ptr<std::mt19937_64> generator)#

Calculates valid (k, d) combinations where k^d ≀ N for fractal edge structure.

This function determines all valid fractal parameters for a judgment node with N possible successor nodes. The fractal structure is defined by:

  • k: Branching factor (number of subdivisions at each level)

  • d: Depth of recursion (number of hierarchical levels)

Algorithm:

  1. Generates all (k, d) pairs where k^d ≀ N

  2. Enforces constraint: d β‰₯ 2 for N > 3 (ensures true fractal structure)

  3. Exception: d = 1 allowed only for N = 2 (binary decision case)

  4. Randomly selects one valid combination from all candidates

Examples:

  • N = 8: possible combinations include (2,3), (2,2), (4,1) β†’ returns one randomly

  • N = 27: possible combinations include (3,3), (9,1), (3,2) β†’ returns one randomly

  • N = 2: only (2,1) is valid (binary split, no true fractal)

The random selection ensures diversity in fractal structures across different judgment nodes, even when they have the same number of edges.

Constraints:

  • k β‰₯ 2 (at least binary branching)

  • d β‰₯ 2 for N > 3 (hierarchical structure required)

  • k^d ≀ N (must not exceed available successor nodes)

Note

Multiple calls with same N can return different (k, d) combinations

Parameters:
  • N – Number of available successor nodes (must be β‰₯ 2)

  • generator – Shared pointer to random number generator for selecting from valid combinations

Returns:

std::pair<int,int> Randomly selected (k, d) combination where k is branching factor and d is depth

inline std::vector<float> randomParameterCuts(int N, std::shared_ptr<std::mt19937_64> generator)#

Generates N random cut points in the interval (0, 1) for production rule parameterization.

This function creates a set of random parameters that define where the feature space should be subdivided. The parameters represent relative positions for cuts in a normalized interval.

Process:

  1. Initializes vector with 0 (left boundary)

  2. Draws N random values from uniform distribution over (0, 1)

  3. Appends 1 (right boundary)

  4. Returns vector: [0, v₁, vβ‚‚, …, vβ‚™, 1]

Important: The returned values are NOT sorted. Sorting and distance calculation is performed separately by sortAndDistance() to obtain actual subdivision ratios.

Example (N=2):

  • Random draws: 0.3, 0.7

  • Returned: [0, 0.3, 0.7, 1]

  • After sortAndDistance(): [0.3, 0.4, 0.3] (three intervals of relative sizes)

See also

sortAndDistance() and fractalLengths() for converting random cuts to fractal interval lengths

Note

Uses std::nextafter(0.0f, 1.0f) to exclude exact 0 and 1 from the random distribution

Parameters:
  • N – Number of random cut points to generate

  • generator – Shared pointer to random number generator for uniform sampling

Returns:

std::vector<float> Vector of size N+2 containing: [0, random₁, randomβ‚‚, …, randomβ‚™, 1]

inline std::vector<float> sortAndDistance(std::vector<float> value)#

Sorts cut points and calculates the relative distances (interval lengths) between consecutive points.

This function converts a set of random cut points into a normalized vector of relative interval lengths that sum to 1.0. This is a crucial step in fractal pattern generation, transforming arbitrary parameter positions into usable subdivision ratios.

Algorithm:

  1. Sorts the input values in ascending order

  2. Calculates distances between consecutive sorted values: dα΅’ = value[i+1] - value[i]

  3. Removes the last element (which would be redundant)

  4. Returns vector of relative lengths

Mathematical property: Since input starts with 0 and ends with 1, the returned distances always sum to exactly 1.0, making them valid relative lengths for partitioning.

Example:

  • Input: [0, 0.4, 0.1, 0.5, 1]

  • Sorted: [0, 0.1, 0.4, 0.5, 1]

  • Distances: [0.1, 0.3, 0.1, 0.5]

  • Verification: 0.1 + 0.3 + 0.1 + 0.5 = 1.0

These relative lengths represent the production rule for fractal generation: when applied recursively, they create a self-similar hierarchical structure.

See also

randomParameterCuts() for generating the input parameters

See also

fractalLengths() for applying these lengths recursively

Note

First element should be 0, last element should be 1 (for valid normalization)

Note

Returned vector has size = input.size() - 1

Parameters:

value – Vector of unsorted cut points (typically from randomParameterCuts(), must start with 0 and end with 1)

Returns:

std::vector<float> Sorted relative distances (interval lengths) between consecutive points, summing to 1.0

inline std::vector<float> fractalLengths(int depth, std::vector<float> parameter)#

Generates fractal pattern of interval lengths through recursive subdivision (L-system style).

This function creates a hierarchical self-similar pattern by recursively applying a production rule to subdivide intervals. It is inspired by L-systems (Lindenmayer systems).

Algorithm:

  1. Start with single unit interval: [1]

  2. For each recursion level (depth iterations):

    • For each current interval with length L:

      • Replace it with k sub-intervals

      • Each sub-interval has length L Γ— parameter[i]

  3. Return the final set of interval lengths

Example (depth=2, parameter=[0.3, 0.7]):

  • Level 0: [1]

  • Level 1: [1Γ—0.3, 1Γ—0.7] = [0.3, 0.7]

  • Level 2: [0.3Γ—0.3, 0.3Γ—0.7, 0.7Γ—0.3, 0.7Γ—0.7] = [0.09, 0.21, 0.21, 0.49]

  • Result: Four intervals lenghts! with fractal distribution summing up to 1

Properties:

  • Total intervals: k^depth (where k = parameter.size())

  • Self-similar: pattern repeats at different scales

  • Sum: If parameters sum to 1, all lengths sum to 1 (normalized)

Biological inspiration: Like plant branching patterns where main branches divide into smaller branches following the same subdivision rule at each level.

See also

random_k_d_combination() for determining (k, depth) parameters

See also

randomParameterCuts() for generatiing the random cuts

See also

sortAndDistance() for generating the production rule parameters

Note

These lengths are mapped to feature boundaries by Node::setEdgesBoundaries()

Parameters:
Returns:

std::vector<float> Fractal pattern of k^depth interval lengths (relative sizes, sum to 1.0 if parameters sum to 1.0)