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:
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:
Initializing the random number generator with the given seed
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:
Iterates through all individuals in the population
For each individual, examines all inner nodes (judgment and processing nodes)
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:
Clears previous decisions (stored in member decisions) and resets node usage tracking
Processes each row in X through the network
Records the decision made for each input sample
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.
See also
- 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
-
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.
See also
- 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
See also
- 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):
Randomly sample N individuals to form a tournament
Select the individual with highest fitness from the tournament
Add the winner to the new population
Elitism (preserving E best individuals):
Identifies the E individuals with highest fitness
Copies them unchanged to the new population
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:
Inner nodes (judgment and processing nodes): apply edgeMutation() with probability probInnerNodes
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.
See also
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:
Iterates through all judgment nodes (type βJβ)
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.
See also
- 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Β²).
See also
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.
See also
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).
See also
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:
Mutates production rule parameters uniformly within valid ranges
Recalculates fractal lengths based on k_d parameters
Regenerates all boundaries to match the new fractal pattern
See also
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:
Shuffles all individual indices randomly
Pairs adjacent individuals in the shuffled order (0-1, 2-3, 4-5, etc.)
Skips pairs where either parent is elite (elite protection)
Node exchange:
Determines maximum exchangeable nodes: min(parent1.size, parent2.size). This is only needed if parents have different network sizes caused by applying callAddDelNodes().
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:
Identifies successor nodes (active subnetwork) in both parents using findSuccessorNodes()
Creates swap maps (old index -> new index) for remapping nodes between parents
Validates that exchanging subnetworks wonβt reduce networks below 2 inner nodes (invalid network)
Swaps nodes up to min(successor1.size, successor2.size) between the subnetworks
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)
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:
Creates a deletion map that remaps indices of nodes after the deleted node (shifting them down by one)
Assigns the deleted node index a random valid edge
Remaps all node IDs and edges in the network to maintain consistency after deletion
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
nSelectedNodessuccessors.
-
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.
See also
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_18Constructor
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:
Identifying unused nodes to delete them in addDelNodes()
Network analysis and optimization
-
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:
Clears the decisions vector and resets node usage flags (see clearUsedNodes())
Initializes traversal at the start nodeβs first edge
Processes each judgment node (corresponding to a feature) and processing node through the network traversal
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:
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
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:
Clearing all node usage flags
Resetting traverse counters for all nodes and the network
Setting the current node to the start nodeβs target
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:
Initialization:
Resets the environment and obtains initial observation
Clears node usage tracking and resets network state
Initializes fitness accumulator and counters
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)
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:
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
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
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
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:
Verifying that each nodeβs ID matches its index in the innerNodes vector
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:
Iterates through all nodes in the network
For each node, examines all outgoing edges
If an edge index exceeds the valid range (β₯ innerNodes.size()), it indicates the edge points to a deleted or non-existent node
If an edge points to the node itself (edge == node.id), creating a self-loop
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:
Updates all node IDs greater than deleted nodeβs ID (decrement by 1)
Updates all edges pointing to nodes after deleted node (decrement by 1)
Redirects edges pointing directly to deleted node using changeEdge()
Updates start node edge if necessary
Decrements jn or pn counter based on deleted node type
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
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:
_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:
Boundary cases:
If v β€ first boundary: return edge 0 (leftmost interval)
If v β₯ last boundary: return last edge index (rightmost interval)
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:
Uniform spacing (lengths vector empty):
Divides the range [minf, maxf] into edges.size() equal intervals
Each interval has width: (maxf - minf) / edges.size()
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:
Draws a random boolean from a Bernoulli distribution with given probability
If true, replaces the current edge target with a new random valid node
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:
Draws a Bernoulli random variable with given probability
If true, samples a new boundary value uniformly from [boundaries[i-1], boundaries[i+1]]
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:
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
After mutating any parameter:
Clears the current boundaries vector
Recomputes fractal lengths using sortAndDistance() and fractalLengths()
Regenerates boundaries using setEdgesBoundaries() with new fractal pattern
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:
Draws a Bernoulli random variable with given probability
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:
Determine k (branching factor) and d (depth) such that k^d equals number of edges
Generate production rule parameters (random cut points in [0,1])
Calculate relative lengths through recursive application of the production rule
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
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:
Generates all (k, d) pairs where k^d β€ N
Enforces constraint: d β₯ 2 for N > 3 (ensures true fractal structure)
Exception: d = 1 allowed only for N = 2 (binary decision case)
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
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:
Initializes vector with 0 (left boundary)
Draws N random values from uniform distribution over (0, 1)
Appends 1 (right boundary)
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:
Sorts the input values in ascending order
Calculates distances between consecutive sorted values: dα΅’ = value[i+1] - value[i]
Removes the last element (which would be redundant)
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:
Start with single unit interval: [1]
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]
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:
depth β Recursion depth (number of hierarchical levels, typically d from random_k_d_combination())
parameter β Production rule ratios (relative lengths for subdivision, typically k values from sortAndDistance() afer randomParameterCuts())
- Returns:
std::vector<float> Fractal pattern of k^depth interval lengths (relative sizes, sum to 1.0 if parameters sum to 1.0)