Gradual Evolution - Climbing Mount Improbable

By RandomMusings | Programming-Puzzles | 20 Jun 2021


I recently watched an amazing lecture series on Evolution by Richard Dawkins. This lecture series sets the argument for evolution as a much better theory when compared to other theories like creationism and intelligent design. A common question that creationists ask about evolution (often in a condescending tone) is - "How can the brain or eye appear by complete chance? The odds are impossible". In this lecture titled Climbing Mount Improbable, Dawkins performs a cool simulation that I have reproduced here myself.

Suppose we have a sentence - “Creationism is a myth”. We call this the target string. This particular string has 18 letters. Given a random sequence of letters, how likely are we to arrive at this particular ‘target string’. One would think that it’s pretty unlikely. And they would be right. We are basically churning an alphabet soup and hoping we end up with the target string. In fact, we can calculate the odds ourselves. There are 26 letters in the alphabet. Since there are 18 letters, the probability of us arriving at that sentence is 1 in 26^18. That comes out to be 1 in 10^21 almost. Pretty staggering odds that it can arise by chance alone. We would have to generate ~10^21 strings (a 1000 trillion trillion) before we can hope to come across it. But let’s modify the question a little bit.

 

How long would it take for a random string to evolve into that particular sentence?

We will start with a random letter jargon called the ‘genesis string’. At each step, we will ‘breed’ the string to obtain 20 offspring, each having some ‘mutations’. Then we will allow natural selection to take its course and choose the ‘best offspring’. We do this until we arrive at the target string. Before we do this, try to guess what this number will be. Compare this with what we obtain in the end.

 

First, we need to elaborate on a few words -

  1. Breed - When we breed a parent string and create an offspring, we derive a string similar to the parent string but with a few minor modifications (like how a child is similar to its parent but not 100%) 
  2. Mutation - These modifications are called mutations. Each letter will have a small probability of being different from its parent. We will call this the mutation rate. 
  3. Best offspring - Remember, we want to evolve the genesis string into the target string. So the best offspring, in this case, will be one (among the 20) that is closest to the target string.

 

Since it’s pretty hard to do this all by hand, we’ll simulate it using python. We have chosen the mutation rate as 0.1

import string
import random

target_string = "CREATIONISM IS A MYTH"
genesis_string = ''.join(random.choices(string.ascii_uppercase, k=18)) #Generate a random string
#Take care of the spaces
genesis_string = genesis_string[:11]+ " " + genesis_string[11:13]+ " "+ genesis_string[13:14] + " " + genesis_string[14:] 
parent_string = genesis_string # The first parent is the genesis string
mutation_rate = 0.1 #Define mutation rate
number_of_offspring = 20 #Define number of offsprings

def degree_of_similarity(offspring, target) : #How many letters are common between the offspring and the target
    similarity = 0
    for index in range(0, len(target)):
        if offspring[index] == target[index] :
            similarity += 1 
    return similarity

def generate_offspring(parent, mutation_rate): #Offspring should be similar to parent except certain locations
    offspring = parent
    for index in range(0, len(parent)):
        random_number = random.uniform(0, 1) 
        if random_number < mutation_rate and offspring[index] != " ":
            temp = list(offspring)
            temp[index] = random.choice(string.ascii_uppercase)
            offspring = "".join(temp)
    return offspring
    
def choose_best_offspring(parent, mutation_rate, number_of_offspring, target): #Choose the best offspring
    best_offspring = ["", 0]
    for index in range(0, number_of_offspring):
        offspring = generate_offspring(parent, mutation_rate)
        similarity = degree_of_similarity(offspring, target)
        if best_offspring[1] <  similarity: #Choose the one with high similarity to target string
            best_offspring = [offspring, similarity]
    return best_offspring[0]

number_of_iterations = 0
while(parent_string != target_string):
    print(parent_string)
    best_offspring = choose_best_offspring(parent_string, mutation_rate, number_of_offspring, target_string) 
    number_of_iterations += 1
    parent_string = best_offspring
print(parent_string)	
print("Number of iterations = " + str(number_of_iterations))

We have defined a few functions to help us do this.

1. degree_of_similarity - Similarity between an offspring and the target string

2. generate_offspring - Generates an offspring from the parent. It will be similar to the parent except for a few locations (where it will be mutated). The mutations are dictated by the mutation rate

3. choose_best_offspring - Invokes the first two functions to determine the best offspring among 20 offsprings

 

When we run this code, we see that we only need 200-300 iterations (it varies with each run as there is a probabilistic component to it) to reach the target string. Compare this with the 1000 trillion trillion attempts required for the non-evolutionary case. We can look at a particular run to determine what is going on in more detail.

0e3e58c0da51974880b7afb646754494b4ac2fb4229e11aa25392d99a6729c71.png

At the start, we begin with an absolutely random string. With each iteration (generation), we begin to see a pattern emerging. Some words are being formed with a few letters misplaced here and there.

9bc1de7264b767bc7721fa8eac7594ae1e9961f374fb6829031e675e27e56231.png

Then the words become clearer. And finally, we arrive at the target string.

10fd7a27a7198a9c287b3946804186e934beccecbc20f81c0dc5ebcd2085839b.png

By going from a system of pure chance to a system of natural selection with mutations, we have significantly increased the odds of reaching the target. From 1 in 1000 trillion trillion -> 1 in 300.

In this experiment, we have performed 'artificial selection'. We take a random string and try to evolve it into the target. Unlike artificial selection, natural selection where there is no target in sight. However, this is still a convincing argument on the power of natural selection. We can clearly see how a complicated string can result from a very primitive string. Similarly, in nature, offsprings that are better equipped to survive pass on their genes to the next generation. Thus the next generation is 'fitter'. Even though these changes are minute on the human scale, on the geological scale they add up resulting in complex features from extremely primitive beginnings. Insurmountable odds of improbability are scaled by a gradual process of improvement from one generation to the next.

If anyone is interested, they can see the lecture series here. Ep3 discusses the evolution of the eye and Ep5 discusses the evolution of the brain.

How do you rate this article?

4



Programming-Puzzles
Programming-Puzzles

Trying out some fun programs

Send a $0.01 microtip in crypto to the author, and earn yourself as you read!

20% to author / 80% to me.
We pay the tips from our rewards pool.