I am trying to create a very simple evolution algorithm for a creature simulator, what I would like to have is, both creatures have a trait and a dominance level, both noted by ints. their child’s trait will be a random number between creature A’s trait and creature B’s trait then skewed to the more dominant. So if A has a trait of 5 and dominance of 2 and B has a trait of 10 and a dominance of 7, the skew will be -5 so it skews more towards B. Their child is more likely to have a trait of 8 than 6. Is there a good way to do this?
I visualise it ending up like this:
A5-6–7—8—-9—–10B
I can’t figure out how much the skew should be until I am able to test the results, so for the time being it’s kind of arbitrary.
Thank you everyone for taking the time to help me.
my solution will give you stronger skew for bigger dominance differences:
it is using the fact that raising a value between 0 and 1 to a positive power will skew the result within the same range.
set the fine-tuning, as you see fit.
import random
a_trait = 5
a_dominance = 2
b_trait = 10
b_dominance = 7
target = random.random()
skew = abs(a_dominance - b_dominance) + 1
fine_tuning_multiplier = 0.3
skewed_target = target ** (skew * fine_tuning_multiplier)
trait_delta = abs(a_trait - b_trait)
target_trait = trait_delta * (1 - skewed_target)
if a_dominance > b_dominance:
val = b_trait - target_trait
else:
val = a_trait + target_trait
print "val:", int(round(val))
note that in this solution the traits must be sorted. (i.e. a_trait < b_trait)
40 consecutive runs resulted in:
8 8 9 8 9 10 6 7 7 10 8 8 9 9 10 9 9 7 10 6 10 7 10 9 10 10 7 8 8 7 10 5 6 6 9 9 10 10 6 6
From what I see, you need a weighted random selection, from range(parent_a.trait, parent_b.trait)
.
To distribute the weights linearly, the simplest thing you could do is construct a range of floats in such a way that the sum()
of the range is 1.0
, and each step would represent a probability of choosing that one step.
Example:
trait_delta = abs(pacent_a.trait - trait_b.trait)
trait_delta_sum = trait_delta * (trait_delta+1.) / 2.
weights = [x/trait_delta_sum for x in range(1, trait_delta+1)]
As I said above, the sum of the weights should be equal to one:
assert sum(weights) == 1.
Then generate a cumulative sum of the weights:
cumulative_weihts = [sum(weights[:x]) for x in range(trait_delta)]
Now just pick a random number, and find the index of the largest number that is smaller than said random number:
rand = random.random()
shift = sum[-1 for x in cumulative_weightn if r<x])
Finally, use the skew as an offset from the trait with the bigger dominance:
if parent_a.dominance > parent_b.dominance:
trait = parent_a.dominance + skew
else:
trait = parent_b.dominance + skew
Sure, that’s quite simple to do.
Just take the sum of both dominance values and take a random.randrange()
value of these.
If that value is lower than the dominance value of parent A, you picked that parent’s trait, otherwise it’s parent B’s trait you picked:
import random
if random.randrange(parentA.dominance + parentB.dominance) < parentA.dominance:
trait = parentA.trait
else:
trait = parentB.trait
In other words, it’s a very simple weighted random selection between two options.
For your specific example, the sum of the dominances is 9, so the randrange()
value is one between 0 and 8; if 0 or 1 is picked, parent A’s trait is selected, if 2, 3, 4, 5, 6, 7 or 8 is picked then parent B’s trait is selected instead.
If instead you are talking about picking a trait on a range from parent A’s trait value up to and including parent B’s trait value where traits are treated as a range, then your dominance values are used to ‘pull’ a trait value towards one or the other parent.
It basically comes down to a tug-of-war between the parents. With balanced dominance, the trait picked would, on average, come down to the average value of the two traits. But with one parent dominating over the other, the trait value picked ‘shifts’ towards the dominating parent.
This translates in a chance that the values below the midpoint are picked for parent A have a chance of parentA.dominance in (parentA.dominance + parentB.dominance), while the values above the midpoint have a chance of parentB.dominance in (parentA.dominance + parentB.dominance) of being picked.
import random
pick = random.random()
# sort parents by trait; smallest trait first
parents = sorted((parentA, parentB), key=attrgetter('trait'))
average = (parents[0].trait + parents[1].trait) / 2.0
weights = []
slots = (parents[1].trait - parents[0].trait + 1.0) * (parents[0].dominance + parents[1].dominance)
for i in range(parents[0].trait, parents[1].trait + 1):
if i <= average:
weights.append(sum(weights) + (parents[0].dominance / slots))
else:
weights.append(sum(weights) + (parents[1].dominance / slots))
if weights[-1] > pick:
return i
A quick demo picking traits between parent A and parent B using the above calculation:
>>> for i in range(40): print pickTrait(),
...
9 9 9 10 6 9 8 10 7 9 9 8 8 8 9 8 9 9 7 5 8 8 9 5 9 9 10 10 10 10 5 5 9 9 8 9 10 7 8 9
6
If I understand your question correctly, you want a probability distribution function somewhat like below. The y axis is your dominance value and the x axis is the trait value.
To generate a value in this distribution, you basically take the area under the curve, 12.5 in this case. Choose a uniform random number between 0 and 12.5, fill in that much area starting from the left side, and see where it falls on the x axis. For example, say you chose 4.5. The area under the curve between 5 and 8 is 4.5, so your child’s trait is 8.
If my math is right, given a
as the value between 0 and 12.5 you chose, d1
and d2
are the respective dominances, and t1
and t2
are the respective traits, your child’s trait is given by:
t1 + sqrt(2*a*(d2-d1)/(t2-t1))
You can do this by generating two random numbers.
Say you have trait A. The first ‘parent’ has a value of 2. The second a value of 8. You thus want a range of 2 to 8. Normally you’d generate a random number between 0 and 1.0, and multiply that by (8 – 2) and add it to 2, like:
N = (rand() * (8-2)) + 2;
Simple enough, but it doesnt trend in a particular direction.
Instead, generate two random numbers. Lets say you want the 8 (the high value) to be the ‘dominant’ trait. Generate two random values between 0 and 1.0, and use the highest of the two when calculating the new child’s value. That will ‘trend’ you towards 8.
If you wanted the low value to be dominant, do the same, using the lower of the two randomly generated numbers.