Genetic Operators
SymbolicOptimization provides operators for generating, mutating, and recombining expression trees.
Tree Generation
tree = generate_tree(grammar; method=GrowMethod(), max_depth=4)
pop = generate_population(grammar, 100; method=HalfAndHalfMethod())Generation methods:
FullMethod— all branches reach maximum depthGrowMethod— branches terminate randomlyHalfAndHalfMethod— mix of full and grow
Mutation
mutated = mutate(tree, grammar) # Auto-select type
mutated = mutate_point(tree, grammar) # Swap operator/variable
mutated = mutate_subtree(tree, grammar) # Replace subtree
mutated = mutate_hoist(tree) # Promote subtree to root
mutated = mutate_constants(tree; std=0.5) # Perturb constants
mutated = mutate_single_constant(tree) # Perturb one constant
mutated = mutate_insert(tree, grammar) # Add operator
mutated = mutate_delete(tree) # Remove operatorCrossover
child1, child2 = crossover(parent1, parent2) # Subtree exchange
child1, child2 = crossover_subtree(parent1, parent2) # Same as above
child1, child2 = crossover_one_point(parent1, parent2) # One-point
child1, child2 = crossover_uniform(parent1, parent2) # Uniform
child1, child2 = crossover_size_fair(parent1, parent2) # Size-fairSimplification
simplified = simplify(tree; grammar=grammar) # Constant folding + algebra
simplified = simplify_algebra(tree) # x + 0 -> x, x * 1 -> x, etc.
simplified = simplify_constants(tree) # 1 + 2 -> 3
normalized = normalize_constants(tree) # Round near-integer constants
clamped = clamp_constants(tree) # Clamp extreme valuesFor deep algebraic simplification, see Advanced Topics.
Operators Reference
SymbolicOptimization.GenerationMethod — Type
GenerationMethodAbstract type for tree generation strategies.
SymbolicOptimization.FullMethod — Type
FullMethod <: GenerationMethodGenerate trees where all branches have exactly the specified depth. All paths from root to leaves have the same length.
SymbolicOptimization.GrowMethod — Type
GrowMethod <: GenerationMethodGenerate trees where branches can terminate early. Produces more varied tree shapes.
SymbolicOptimization.HalfAndHalfMethod — Type
HalfAndHalfMethod <: GenerationMethodGenerate half the trees with Full method, half with Grow method. Provides good diversity in initial populations.
SymbolicOptimization.generate_tree — Function
generate_tree(grammar::Grammar; kwargs...) -> AbstractNodeGenerate a random expression tree from the grammar.
Keyword Arguments
method::GenerationMethod = GrowMethod(): Generation strategymin_depth::Int = 1: Minimum tree depthmax_depth::Int = 4: Maximum tree depthtarget_type::Symbol = :Any: Target output type (for typed grammars)rng::AbstractRNG = Random.GLOBAL_RNG: Random number generator
Examples
g = Grammar(binary_operators=[+, *], unary_operators=[sin], variables=[:x])
tree = generate_tree(g, max_depth=3)
tree = generate_tree(g, method=FullMethod(), max_depth=4)
tree = generate_tree(g, method=HalfAndHalfMethod(), min_depth=2, max_depth=5)SymbolicOptimization.generate_population — Function
generate_population(grammar::Grammar, n::Int; kwargs...) -> Vector{AbstractNode}Generate a population of random trees.
Arguments
grammar: The grammar to usen: Number of trees to generate
Keyword Arguments
Same as generate_tree, plus:
unique::Bool = false: If true, ensure all trees are structurally uniquemax_attempts::Int = n * 10: Maximum attempts when generating unique trees
SymbolicOptimization.random_terminal — Function
random_terminal(grammar::Grammar; target_type=:Any, rng=Random.GLOBAL_RNG) -> AbstractNodeGenerate a random terminal node (variable or constant).
SymbolicOptimization.generate_random_subtree — Function
generate_random_subtree(grammar::Grammar; max_depth=3, target_type=:Any, rng=Random.GLOBAL_RNG) -> AbstractNodeGenerate a random subtree with the given maximum depth. Alias for generate_tree with GrowMethod.
SymbolicOptimization.mutate — Function
mutate(tree::AbstractNode, grammar::Grammar; kwargs...) -> AbstractNodeApply mutation to an expression tree.
Keyword Arguments
rng::AbstractRNG = Random.GLOBAL_RNG: Random number generatorp_point::Float64 = 0.3: Probability of point mutationp_subtree::Float64 = 0.3: Probability of subtree mutationp_hoist::Float64 = 0.1: Probability of hoist mutationp_constant::Float64 = 0.2: Probability of constant perturbationp_insert::Float64 = 0.05: Probability of insert mutationp_delete::Float64 = 0.05: Probability of delete mutationmax_depth::Int = 4: Maximum depth for new subtreesconstant_std::Float64 = 0.5: Standard deviation for constant perturbation
Returns a new mutated tree (original is not modified).
SymbolicOptimization.mutate_point — Function
mutate_point(tree::AbstractNode, grammar::Grammar; rng=Random.GLOBAL_RNG) -> AbstractNodeReplace a random node with another node of the same arity/type.
- Function nodes: Replace with another function of same arity
- Variables: Replace with another variable
- Constants: Replace with a new random constant
SymbolicOptimization.mutate_subtree — Function
mutate_subtree(tree::AbstractNode, grammar::Grammar; rng=Random.GLOBAL_RNG, max_depth=4) -> AbstractNodeReplace a random subtree with a newly generated subtree.
SymbolicOptimization.mutate_hoist — Function
mutate_hoist(tree::AbstractNode; rng=Random.GLOBAL_RNG) -> AbstractNodeReplace the tree with one of its subtrees, effectively "hoisting" it up. Tends to simplify/shrink trees.
SymbolicOptimization.mutate_constants — Function
mutate_constants(tree::AbstractNode; rng=Random.GLOBAL_RNG, std=0.5) -> AbstractNodePerturb all constants in the tree by adding Gaussian noise.
SymbolicOptimization.mutate_single_constant — Function
mutate_single_constant(tree::AbstractNode; rng=Random.GLOBAL_RNG, std=0.5) -> AbstractNodePerturb a single randomly selected constant.
SymbolicOptimization.mutate_insert — Function
mutate_insert(tree::AbstractNode, grammar::Grammar; rng=Random.GLOBAL_RNG) -> AbstractNodeInsert a new operator above a random node, making the original node a child. Tends to grow trees.
SymbolicOptimization.mutate_delete — Function
mutate_delete(tree::AbstractNode; rng=Random.GLOBAL_RNG) -> AbstractNodeDelete a function node by replacing it with one of its children. Tends to shrink trees.
SymbolicOptimization.crossover — Function
crossover(parent1::AbstractNode, parent2::AbstractNode; kwargs...) -> Tuple{AbstractNode, AbstractNode}Perform crossover between two parent trees, producing two offspring.
Keyword Arguments
rng::AbstractRNG = Random.GLOBAL_RNG: Random number generatormax_depth::Int = 10: Maximum depth of resulting treesinternal_prob::Float64 = 0.9: Probability of selecting internal (non-terminal) nodes
Returns a tuple of two new trees (originals are not modified).
SymbolicOptimization.crossover_subtree — Function
crossover_subtree(parent1::AbstractNode, parent2::AbstractNode; kwargs...) -> Tuple{AbstractNode, AbstractNode}Standard subtree crossover (Koza-style).
Selects a random subtree from each parent and swaps them.
SymbolicOptimization.crossover_one_point — Function
crossover_one_point(parent1::AbstractNode, parent2::AbstractNode; kwargs...) -> Tuple{AbstractNode, AbstractNode}One-point crossover: only the first child is produced by swapping subtrees. The second child is the other swap direction.
SymbolicOptimization.crossover_uniform — Function
crossover_uniform(parent1::AbstractNode, parent2::AbstractNode; kwargs...) -> Tuple{AbstractNode, AbstractNode}Uniform crossover: at each node position, randomly choose from either parent.
Note: This only works well when parents have similar structure. Falls back to subtree crossover if structures are too different.
SymbolicOptimization.crossover_size_fair — Function
crossover_size_fair(parent1::AbstractNode, parent2::AbstractNode; kwargs...) -> Tuple{AbstractNode, AbstractNode}Size-fair crossover: tries to select subtrees of similar size to avoid bloat.
SymbolicOptimization.simplify — Function
simplify(tree::AbstractNode; grammar::Union{Grammar,Nothing}=nothing, iterations::Int=3) -> AbstractNodeApply all simplification passes repeatedly until the tree stabilizes.
Arguments
tree: The tree to simplifygrammar: Optional grammar for evaluation during constant folding. If provided withisempty(grammar.constants), no new constants will be created during simplification.iterations: Maximum number of simplification passes
SymbolicOptimization.simplify_algebra — Function
simplify_algebra(tree::AbstractNode; grammar::Union{Grammar,Nothing}=nothing) -> AbstractNodeApply basic algebraic simplification rules.
Rules include:
x + 0 → x,x - 0 → x,x * 1 → x,x / 1 → xx * 0 → 0,0 / x → 0(only if constants allowed)x - x → 0,x / x → 1(only if constants allowed)x ^ 0 → 1,x ^ 1 → x(constant creation only if allowed)sin(0) → 0,cos(0) → 1, etc. (only if constants allowed)
When grammar is provided and has no constants (isempty(grammar.constants)), rules that would create new constant nodes are skipped. This ensures that constant_range = nothing is respected throughout the optimization process.
SymbolicOptimization.simplify_constants — Function
simplify_constants(tree::AbstractNode; grammar::Union{Grammar,Nothing}=nothing) -> AbstractNodeEvaluate and fold constant subexpressions.
Replaces subtrees that contain only constants with their evaluated result.
Note: This function only folds existing constants - it does not create new ones from variable expressions. Use simplify_algebra for algebraic simplifications.
SymbolicOptimization.normalize_constants — Function
normalize_constants(tree::AbstractNode; digits::Int=6) -> AbstractNodeRound all constants to a specified number of digits. Helps with numerical stability and canonical forms.
SymbolicOptimization.clamp_constants — Function
clamp_constants(tree::AbstractNode; min_val::Float64=-1e6, max_val::Float64=1e6) -> AbstractNodeClamp all constants to a range to prevent extreme values.