Anti-Weasel programs

A forum for discussion and criticism of specialized topics relevant (pro and con) to Intelligent Design (ID) -- design detection, design specification, irreducible complexity, origin of life, platonic forms, design matrix, population genetics, cybernetic theories, semiotic theories, Fishers's fundamental theorem, Kimura's neutral evolution, Darwinian evolution, modern synthesis, probability theories, fine tuning, typology, discontinuity systematics, steganography, evolutionary algorithms, published ID material, ID philosophy, front loaded evolution, omega point theory, anthropic principles, multiverses and many-worlds, panspermia, extra terrestrials, teleology in biology, redundant complexity and fault tolerance, algorithmic complexity, complexity measures, no free lunch, blindwatchmakers, bad design, evil design, junk DNA, DNA grammars, von Neumann replicators vs. autocatalysis, Quines, polyconstrained DNA, Mendel's Accountant, DNA skittle, re-association kinetics, molecular clocks, GGU/GID models, enigma of consciousness and Quantum Mechanics, Turing machines, Lenski's bacteria, thermodynamics, Avida, self organization, self disorganization, generalized entropy, Cambrian explosion, genetic entropy, Shannon information, proscriptive information, Programming of Life, law of large numbers, etc.

Anti-Weasel programs

Postby stcordova » Sat Mar 10, 2018 2:53 pm

Dawkins Weasel program selects for a particular string. It is like selecting for a password. But since the intermediate forms in biological systems must be viable, it might be fair to say, REAL natural selection in the wild will select against (not for) partially functioning systems. What good is a blind eye due to half formed proteins and systems?

Bill Dembski, Robert Marks, WInston Ewert tried to argue against Darwinian evolution using the conservation of information. I argue for a simpler framework. What if natural selection selects against change?????

One can easily see an illustration of the problem of fitness peaks, especially for increasingly large complex systems like software. Take the simple example of compilability (syntacticall correctness) of a C program. This is about the simplest syntactically correct C program I can envision:

Code: Select all

One could of course strip out the spaces. How can this evolve via selection and mutation? Well a lot of intermediate forms will be shot down immediately as random letters are thrown at the code. I mean, how hard would it be to evolve even a simple "for loop"? What is needed is simultaneous mutations, not gradual changes since gradual changes will be selected AGAINST, not for.

This is brutally evident in some (not all) biological processes. Many functions are genetically coded in manners akin to formal grammers. Fully formed transactional systems are the order of the day for many systems.

Furthermore, experimentally we have seen random mutation and selection selecting for SIMPLER functions, nto more complex ones in the real world. In fact, on average this seems to be totally the case. One could say that real evolution resists the evolution of fragile, complex, extravagant systems such as in the mating system of Peacocks. The problem of the Peacock's tail (which is a complex, fragile, extravagant system) troubled Darwin greatly.
Posts: 444
Joined: Wed Mar 05, 2014 1:41 am

Re: Anti-Weasel programs

Postby Eric Holloway » Sat Mar 10, 2018 4:16 pm

The simplest model is one where a single corruption causes an organism to stop reproducing.

An organism is represented by a bitstring of length L.

Each bit has a probability p of being flipped.

Consequently, the probability of the organism being corrupted is Pr{Corrupt} = 1-(1-p)L.

Here is a simulation in Python where you can see the population complexity quickly hits a plateau.

Run the code online here:

Code: Select all
# This simulation shows random mutation and
# selection causes organism complexity to stop
# increasing.  This is because the more complex an
# organism is, the more likely its genome will
# become corrupted by mutation and it will cease
# being able to reproduce.
# Each organism is represented by a bitstring of
# length L.  L is the organism's complexity. 
# Each bit in the bitstring has
# probability p of becoming corrupt each
# generation.  Consequently, an organism has a
# probability of 1-(1-p)**L of becoming corrupt
# each generation.
# The simulation initializes the population with a
# single individual of length L=1.  Each
# generation, the individual will create two
# offspring with lengths of L-1 and L+1.  There is
# a probability of 1-(1-p)**L each individual
# becomes corrupted each generation, and is
# subsequently removed from the next generation.
# As the simulation runs you will first see the the
# maximum length organism in the population will
# increase each generation.  However, the
# population hits a plateau beyond which complexity
# does not increase.  This happens because larger
# organisms become corrupted with a higher
# probability, and eventually they become too
# easily corrupted to continue creating larger
# offspring.
# This idea was originated by Salvador Cordova.
from random import random

# Initialize the population with
# a single organism of length 1.
L = 1
pop = [L]

# This is the probability a bit will
# flip and corrupt the organism.
p = 0.01

# Evolve the population.
while len(pop) > 0:
    # Print the largest organism in the population.

    # Initialize the new population.
    old_pop = pop[:]
    pop = []

    # Create the next population.
    for L in old_pop:
        # If the onganism does not become corrupted,
        # it stays in the gene pool and creates
        # a shorter and longer offspring.
        # Otherwise, the organism is removed.
        if random() > 1-(1-p)**L:
            pop += [L]
            if L > 1: pop += [L-1]
            pop += [L+1]

    # Cull the population to 1 of each length
    # so the simulation doesn't crash.
    pop = list(set(pop))
Last edited by Eric Holloway on Sun Mar 11, 2018 7:50 pm, edited 1 time in total.
Eric Holloway
Posts: 5
Joined: Mon Mar 05, 2018 1:46 am

Re: Anti-Weasel programs

Postby stcordova » Sat Mar 10, 2018 7:47 pm

Hi Eric,

Thanks for your program. I ran it. I like the output results. I'm going to have to brush up on Python syntax to understand your program. I tried to modify it with L starting at 100, but I didn't know how to create a starting population size.

What is the intial population size.

What does this mean?

pop = [ ]

I was proficient in C, so I probably just need a little acquainting with Python.
Posts: 444
Joined: Wed Mar 05, 2014 1:41 am

Re: Anti-Weasel programs

Postby Eric Holloway » Sun Mar 11, 2018 7:56 pm

Python's main datastructure is a list, which is made by the square brackets.

The statement:
Code: Select all
pop = []

means create a list and assign it to the variable pop.

You can initialize the list with values by putting them between the square brackets. For instance,
Code: Select all
pop = [1,2,3,4]

creates an initial population with four individuals of lengths 1, 2, 3, and 4.

Currently, the simulation starts with a population of one individual of length 1.
With a corruption probability of 0.01 per bit, the population plateaus around 60, so starting at 100 will most likely lead to extinction.

You can mess around with Python using this website:

The nice thing about the website is you can try out simple statements on the console in the right hand window, and write and run more complex programs in the notepad on the left.

In general, Python has a lot of useful datastructures built into the language, which in C you'd have to laboriously hand craft. I highly recommend Python for all your prototyping needs. Nowadays I only dip into Java and C if I really need speed. But, that rarely happens.
Eric Holloway
Posts: 5
Joined: Mon Mar 05, 2018 1:46 am

Re: Anti-Weasel programs

Postby Eric Holloway » Sun Mar 25, 2018 7:55 am

Two objections as to how the organism length increases:

1. If the population increases exponentially.

2. If the redundancy increases linearly.

I wrote two additional simulations to test these objections. The original simulation had only one organism of each length.

The first new simulation "" will allow any number of organisms of each length. This way, the smaller length populations can grow to support the longer length organisms.

Here is a graph of the results from the first new simulation "". We can see the population must grow faster than exponential to allow organism length to increase indefinitely. The numbers seem quite unrealistic in a biological setting.

This chart shows the results of the simulation "". We see that the population must grow faster than exponential to support indefinite organism length increase.
exponential.png (24.44 KiB) Viewed 2594 times

This is the "" simulation source code.

Code: Select all
# The population is grouped based on length.
pop = {1 # Length of organism in population.
       10} # Number of organisms of that length.
p = 0.01 # Probability of a bit becoming corrupted.

for generation in range(600):
    old_pop = dict(pop)
    pop = {}

    # For each length group L in the population,
    # generate offspring of a slightly shorter,
    # the same, and slightly longer length.
    # L is the length and N is the group size.
    for L, N in old_pop.items():
        # Survival probability gives expected number of offspring.
        new_offspring_amount = int(N * (1-p)**L)

        # Increase size of length groups.
        if L > 1: pop[L-1] = pop.get(L-1, 0) + new_offspring_amount
        pop[L] = pop.get(L, 0) + new_offspring_amount
        pop[L+1] = pop.get(L+1, 0) + new_offspring_amount

    # Print population size necessary to maintain maximum length.
    largest_organism_length = max([L for L in pop.keys() if pop[L] > 0])
    total_population_size = int(sum(pop.values()))
    print largest_organism_length, total_population_size

The second new simulation "" increases redundancy to maintain a constant survival probability as the organism length increases.

The graph from "" shows the redundancy must increase exponentially, which means the unique functionality becomes an infinitesimal portion of the genome as its length increases. This seems biologically implausible in light of the ENCODE project, showing the majority of the genome is functional.

This chart produced from the "" simulation results shows the redundancy increase to maintain a constant survival probability. It grows at an exponential rate. This means for a unique function of length X, the genome's unique functionality is only X/2^X, approaching zero in the limit.
redundancy2.png (24.55 KiB) Viewed 2594 times

Here is the source code for "".

Code: Select all
# n choose k function.
def nCk(n, k):
    r = 1
    for i, j in enumerate(range(k+1, n+1)):
        r *= j/float(i+1)
    return r

# Redundancy counter increments whenever
# current redundancy does not meet
# probability threshold.
R = 1

# Probability a bit will not be corrupted.
bp = 0.9

# Probability threshold an organism will survive.
survival_probability_threshold = 0.9

# Increase the functional sequence length (L).
# For each iteration, also increase the redundancy
# until the survival threshold is met.
for L in range(1, 50):
    # The survival probability is the
    # probability at least one redundant
    # section has no corrupted bits.
    survival_probability = sum([nCk(R, i) * bp**(L*i) * (-1)**(i-1) for i in range(1,R+1)])

    # Increase redundancy until survival
    # probability meets threshold.
    while survival_probability < survival_probability_threshold:
        R += 1
        survival_probability = sum([nCk(R, i) * bp**(L*i) * (-1)**(i-1) for i in range(1,R+1)])
    print L, L*R

In conclusion, we see that the two objections require biologically implausible rate increases to be sustained.
Eric Holloway
Posts: 5
Joined: Mon Mar 05, 2018 1:46 am

Re: Anti-Weasel programs

Postby Eric Holloway » Mon Mar 26, 2018 9:44 pm

Here's an explanation of the mysterious survival probability calculation from "":

Code: Select all
survival_probability = sum([nCk(R, i) * bp**(L*i) * (-1)**(i-1) for i in range(1,R+1)])

To understand how we arrive at this calculation, let's look at a 2 bit example with a redundant copy, giving us 4 bits. For simplicity, 0 will be a good bit and 1 will be a corrupt bit. The bit corruption probability is 0.1.

Now, lets calculate the probability of each 4 bit bitstring, and mark with a * those that survived (at least one copy with no corruptions).

    0000 0.6561 *
    1000 0.0729 *
    0100 0.0729 *
    1100 0.0081 *
    0010 0.0729 *
    1010 0.0081
    0110 0.0081
    1110 0.0009
    0001 0.0729 *
    1001 0.0081
    0101 0.0081
    1101 0.0009
    0011 0.0081 *
    1011 0.0009
    0111 0.0009
    1111 0.0001

If we add together all the * entries, we get 0.9639.

The code that generated the list follows.

Code: Select all
def dec2bin(dec, length):
    bin = []
    while dec > 0:
        if dec % 2 == 1:
            bin += [1]
            dec -= 1
            dec /= 2
            bin += [0]
            dec /= 2
    bin += [0] * (length - len(bin))
    return bin

def prob(perm, p):
    pr = 1
    for d in perm:
        if d == 0:
            pr *= (1-p)
            pr *= p
    return pr

L = 2
r = 2
p = 0.1
perms = []
for i in range(2**(L*r)):
    perms += [dec2bin(i, (L*r))]

c = 0
for perm in perms:
    bs = "".join([str(d) for d in perm])
    if all([any(perm[L*i:L*(i+1)]) for i in range(r)]):
        print bs, prob(perm, p)
        c += prob(perm, p)
        print bs, prob(perm, p), "*"

print "Probability of survival", 1 - c       

This approach becomes intractable pretty quickly. A gene of 4 bits with 5 copies requires checking 220 bitstrings.

We can simplify the calculation with combinatoric math. A bitstring schema has _ in the bitstring, which stands for either 0 or 1. So, the schema 000_ stands for both 0000 and 0001. A schema's probability is the sum of all probabilities of bitstrings that match the schema. We denote the probability of a bitstring and a schema with p(000_). So, p(000_) = p(0000)+p(0001) = 0.6561+0.0729 = 0.729.

We can see from the bitstring probability list that the survival probability is calculated by p(00__)+p(__00)-p(0000). The reason why we subtract p(0000) is because both p(00__) and p(__00) contain p(0000), and it gets double counted.

With our notation, let's expand our example to 3 copies, but only with 1 bit per gene, so we can still easily list all the possibilities.

    000 0.729 *
    100 0.081 *
    010 0.081 *
    110 0.009 *
    001 0.081 *
    101 0.009 *
    011 0.009 *
    111 0.001

The survival probability for this example is 0.999.

It might be tempting to repeat our previous example and say the total probability is calculated with p(00_)+p(0_0)+p(_00)-p(000). However, this gives us 0.729+0.081+0.729+0.081+0.729+0.081-0.729=1.701.

One problem is p(000) is counted more than twice, unlike the last example. It is counted three times. However, even if we subtract it twice, to make up for the additional count, we still don't get the right answer. Now, we get 0.927, too small.

Referring back to our list, we see that we missed the 0.009 probabilities. We could represent these with the schemas p(1__)+p(_1_)+p(__1). However, if we stick with using 0s, p(0__)+p(_0_)+p(__0), we will end up counting more bitstrings too frequently. Something to notice, though, is that both 0__ and _0_ contain 00_.

So, perhaps we can add the double underscore schemas, 0__, and subtract the single underscore schemas, 00_. How many single underscore schemas need to be subtracted? Since both 0__ and _0_ contain 00_, then one p(00_) has to be subtracted. Including all the single and double underscore schemas, we get:


Since each of these schemas also includes p(000), all the p(000) instances represented cancel out. Thus, all that remains is to add a p(000) to the above summation:


Now we can see the general form, it remains to calculate the probability. The schemas with a single underscore (S[1]) all have the same probability, as do the double underscore (S[2]) and no underscore (S[0]) schemas. So, we can merely multiply the probability of each schema type by its number of members.


You may notice these multipliers look a bit like Pascal's triangle:

Code: Select all
  1 1
 1 2 1
1 3 3 1

The elements can be calculated using the n-choose-k function, e.g. the fourth line can be calculated with [nCk(k,3) for k in range(4)]. This turns our previous summation into:


The above pattern is known as the inclusion-exclusion principle. ... _principle

The final piece of our puzzle is calculating the probabilities for the schemas. What we are calculating is how many uncorrupted genes are in the schema, and summing over all the other genes. An uncorrupted gene's probability is (1-p)L, where L is the length of the gene. Two uncorrupted genes is (1-p)2L. Since we are summing over all the other genes, we move the uncorrupted genes out of the summation. The remaining genes, since we are including all their corrupted and uncorrupted variants, sum to 1. Thus, the probability of a schema, where B is the number of uncorrupted genes out of R copies, is S[B]=(1-p)BL, and we can generalize our summation using q=1-p as:


From this example we can see the pattern for R copies is:


The Python source for this expression is, where bp is used instead of q:

Code: Select all
survival_probability = sum([nCk(R, i) * bp**(L*i) * (-1)**(i-1) for i in range(1,R+1)])
Eric Holloway
Posts: 5
Joined: Mon Mar 05, 2018 1:46 am

Re: Anti-Weasel programs

Postby Eric Holloway » Tue Mar 27, 2018 1:09 pm

Another objection is the model does not account for beneficial mutations.

This is simple to address. We add a new variable, q, to be the probability a mutation is harmful.

Whereas in the original model, all mutations are harmful, with the addition of q, then the survival probability (s) is calculated as:

s = (1-pq)^L

where p is the probability a bit is flipped and L is the length of the organism.

Now, we can solve for q, giving us:

q = (1-s^(1/L))/p

In the limit as L approaches infinity (oo), the equation becomes:

q = (1-s^(1/oo))/p = (1-s^0)/p = (1-1)/p = 0/p = 0

The graph looks like:

This graph shows that as the length of the organism increases, almost all mutations become non-harmful. Value for p and s is 0.9.
beneficial.png (22.12 KiB) Viewed 2571 times

This means that as the organism becomes more complex, then for a constant survival rate to be maintained most of the mutations must become non-harmful.
Eric Holloway
Posts: 5
Joined: Mon Mar 05, 2018 1:46 am

Return to Intelligent Design