Natural selection is a mechanism of evolution.
Organisms that are more adapted to their environment are more likely to survive and pass on the genes that aided their success.
This process causes species to change and diverge over time.
Natural selection is one of the ways to account for the millions of species that have lived on Earth.
For example, the evolution of long necks has enabled giraffes to feed on leaves that others cannot reach, giving them a competitive advantage.
Owing to this better food source, individuals with longer necks were more likely to survive, while those with shorter necks and access to less food were less likely to survive.
Giraffes carrying different genes from one another
A simple simulation is built based on the basic concepts of natural selection.
First, an
environment and
populations that carry the
genes must be created.
The worms live in a two-dimensional environment, and each worm has the following genes that allow it to survive by eating feeds:
- speed: the moving speed of the worm.
- sense: the distance within which the worm can sense feeds.
- switch: the probability of changing direction when no feeds lie within the sensing distance.
The class is defined as below. Worms act through the
moving function, which interacts with the genes.
class WormGene:
def __init__(self, speed: int, sense: float, switch: float) -> None:
self.speed = speed
self.sense = sense
self.switch = switch
class Worm(WormGene, WormHelper):
def __init__(
self,
speed: int = EnvironmentConsts.WORM_SPEED_ORIGINAL,
sense: float = EnvironmentConsts.WORM_SENSE_ORIGINAL,
switch: float = EnvironmentConsts.WORM_SWITCH_ORIGINAL
):
WormGene.__init__(self, speed, sense, switch)
self._initialize()
(...)
def moving(self, apples: List[Apple]) -> None:
self.direction: str
self.direction = self._get_random_direction(self.direction, self.switch)
nearest_apple = self._get_nearest_apple(self.body[-1], apples, self.sense)
if nearest_apple is not None:
self.direction = self._get_direction_by_sense(self.body[-1], nearest_apple, self.direction)
self.body: List[List[int]]
self.body = self._get_moved_body(self.body, self.direction, self.speed)
The full code for the
Worm class is available at the following link. The environment and worms operate as shown below in Fig.2: An operation of the environment and worms.
Depending on whether the worm's sensing circle contains apples, each worm follows a different operation method.
If no apples are within the sensing circle, the worm determines its direction randomly based on the
switch gene.
Otherwise, the angle between the worm's movement direction vector and the worm-to-apple vector is computed, and the angular difference is used to
select the vector with the highest similarity.
The
code shows the details of the worm's operation method
_get_direction_by_sense(), and the method for finding the direction most similar to an apple is described mathematically as follows:
\( \mathbf{h} = (hx, hy)\) as the position of the worm's head
\( \mathbf{a} = (ax, ay)\) as the position of the nearest apple, adjusted by half the worm size to center on the apple
\( \mathbf{v}_{ha} = (a - h)\) as the vector from the worm's head to the apple
\( \theta_{ha} = arctan2(\mathbf{v}_{ha_y}, \mathbf{v}_{ha_x})\) as the angle in radians of \( \mathbf{v}_{ha}\)
\( \mathbf{U}\) as the set of unit vectors for possible movement directions, excluding the reverse of the current direction
\( \textit{D}\) as the set of corresponding movement directions
\( \theta_{u}\) as the angle in radians of each unit vector \(u \in U\)
\( \Delta\theta_{u, h_a} = | \theta_{ha} - \theta_{u} |\) as the absolute angular difference between \( \mathbf{v}_{ha} \) and each \(u\)
\( d_{\min} = \arg \min_{d \in D} (\Delta \theta_{u, ha})\)
An operation of the environment and worms
All preparations for the simulation are now complete. Natural selection can be simulated as shown below in Fig.3: A part of the natural selection simulation.
The
generation,
population, and
seed are fixed for the reproducibility of the natural selection simulations, and the surviving genes are observed.
Note that worms that do not eat even a single
apple within one generation die.
A part of the natural selection simulation
The parameters for generation, population, and seed are set as shown below. The results are then logged and visualized to observe how genes evolve and survive.
- generation: 100
- population: 50
- seed: 6
The visualized results are examined below. The x-axis represents the generation and the y-axis represents each gene. In each figure, a line is drawn for the
average genes per generation.
The configured number of generations is 100, but the simulation stops at generation 70 because only one worm survives after that point.
The
Speed and
Sense genes tend to increase gradually over generations. Detecting apples and moving quickly to eat them is naturally advantageous for survival.
The
Switch gene starts at 0.5, and the final evolved worm also returns to 0.5. In this environment, changing direction too frequently appears to work against survival.
Speed
Sense
Switch
Lastly, the data of the super worm that survived until the end of the environment is reproduced to conclude the post.
The super worm, which moves remarkably fast
Speed: 13
Sense: 450
Switch: 0.48