forked from EMI-Group/evogp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinsert.py
More file actions
87 lines (70 loc) · 3.19 KB
/
Copy pathinsert.py
File metadata and controls
87 lines (70 loc) · 3.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
from typing import Optional, Tuple
import torch
from torch import Tensor
from .base import BaseMutation
from ...tree import Forest, MAX_STACK, randint, GenerateDescriptor
from .mutation_utils import vmap_subtree
class InsertMutation(BaseMutation):
"""
InsertMutation implements a mutation strategy where a random basic operator (subtree) is inserted
into a GP individual at a randomly selected position. This operation helps introduce new structure
into the individual by replacing part of the original tree with a newly generated subtree.
"""
def __init__(
self,
mutation_rate: float,
descriptor: GenerateDescriptor,
):
"""
Args:
mutation_rate (float): The probability of each individual undergoing mutation. Should be between 0 and 1.
descriptor (GenerateDescriptor): The descriptor used to generate random subtrees for mutation.
"""
self.mutation_rate = mutation_rate
self.descriptor = descriptor
def __call__(self, forest: Forest):
"""
Perform the insert mutation by selecting a random subtree from the individual and inserting
it into a random position within the tree.
This mutation introduces new structure into the individual by replacing part of the original tree
with a newly generated subtree.
Args:
forest (Forest): The current population of trees (Forest object).
Returns:
Forest: The updated population after mutation, where some individuals have undergone the insert operation.
"""
# Determine which trees need to mutate based on the mutation rate
mutate_indices = torch.rand(forest.pop_size) < self.mutation_rate
# If no trees are selected for mutation, return the original forest
if mutate_indices.sum() == 0:
return forest
# Extract the subset of trees that need to mutate
forest_to_mutate = forest[mutate_indices]
# Generate random mutation positions for selected trees
mutate_positions = randint(
size=(forest_to_mutate.pop_size,),
low=0,
high=forest_to_mutate.batch_subtree_size[:, 0],
dtype=torch.int64,
)
# Extract subtrees from the selected mutation positions
subtrees = vmap_subtree(forest_to_mutate, mutate_positions)
# Generate new trees (subtrees) to insert into the individual
newtrees = Forest.random_generate(
pop_size=forest_to_mutate.pop_size,
descriptor=self.descriptor,
)
# Generate positions within the new trees where subtrees will be inserted
newtrees_positions = randint(
size=(newtrees.pop_size,),
low=1,
high=newtrees.batch_subtree_size[:, 0],
dtype=torch.int32,
)
# Insert the subtrees into the new trees at selected positions
newtrees = newtrees.mutate(newtrees_positions, subtrees)
# Insert the new trees into the original forest at the selected positions
forest[mutate_indices] = forest_to_mutate.mutate(
mutate_positions.to(torch.int32), newtrees
)
return forest