Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
172 lines (150 sloc) 6.26 KB
"""
`EvoLife`_ ported to Xentica.
.. _EvoLife: https://github.com/a5kin/evolife
"""
from xentica import core
from xentica import seeds
from xentica.tools import xmath
from xentica.core import color_effects
from xentica.tools.color import GenomeColor
from xentica.tools.genetics import genome_crossover
from xentica.tools.rules import LifeLike
from xentica.seeds.random import RandInt
from examples.base import RegularCA, RegularExperiment
from examples.base import run_simulation
class EvoLife(RegularCA):
"""
Life-like cellular automaton with evolutionary rules for each cell.
Rules are:
- Each living cell has its own birth/sustain ruleset and an energy
level;
- Cell is loosing all energy if number of neighbours is not in its
sustain rule;
- Cell is born with max energy if there are exactly N neighbours
with N in their birth rule;
- Same is applied for living cells (re-occupation case), if
new genome is different;
- If there are several birth situations with different N possible,
we choose one with larger N;
- Newly born cell's ruleset calculated as crossover between
'parent' cells rulesets;
- If cell is involved in breeding as a 'parent', it's loosing
BIRTH_COST units of energy per each non-zero gene passed;
- This doesn't apply in re-occupation case;
- Every turn, cell is loosing DEATH_SPEED units of energy;
- Cell with zero energy is dying;
- Cell cannot have more than MAX_GENES non-zero genes in ruleset.
"""
energy = core.IntegerProperty(max_val=255)
rule = core.TotalisticRuleProperty(outer=True)
rng = core.RandomProperty()
death_speed = core.Parameter(default=15)
max_genes = core.Parameter(default=9)
mutation_prob = core.Parameter(default=.0)
def __init__(self, *args, legacy_coloring=False):
"""Support legacy coloring as needed."""
self._legacy_coloring = legacy_coloring
super().__init__(*args)
def emit(self):
"""Broadcast the state to all neighbors."""
for i in range(len(self.buffers)):
self.buffers[i].energy = self.main.energy
self.buffers[i].rule = self.main.rule
def absorb(self):
"""Apply EvoLife dynamics."""
# test if cell is sustained
num_neighbors = core.IntegerVariable()
for i in range(len(self.buffers)):
num_neighbors += xmath.min(1, self.neighbors[i].buffer.energy)
is_sustained = self.main.rule.is_sustained(num_neighbors)
# test if cell is born
fitnesses = []
for i in range(len(self.buffers)):
fitnesses.append(core.IntegerVariable(name="fit%d" % i))
num_parents = core.IntegerVariable()
for gene in range(len(self.buffers)):
num_parents *= 0 # hack for re-init variable
for i in range(len(self.buffers)):
is_alive = xmath.min(1, self.neighbors[i].buffer.energy)
is_fit = self.neighbors[i].buffer.rule.is_born(gene + 1)
num_parents += is_alive * is_fit
fitnesses[gene] += num_parents * (num_parents == (gene + 1))
num_fit = core.IntegerVariable()
num_fit += xmath.max(*fitnesses)
# new energy value
self.main.energy = (self.main.energy - self.meta.death_speed) * \
(self.main.energy > self.meta.death_speed)
self.main.energy *= is_sustained
self.main.energy |= 255 * (num_fit > 0) * (self.main.energy == 0)
# neighbor's genomes crossover
genomes = []
for i in range(len(self.buffers)):
genomes.append(core.IntegerVariable(name="genome%d" % i))
for i in range(len(self.buffers)):
is_fit = self.neighbors[i].buffer.rule.is_born(num_fit)
genomes[i] += self.neighbors[i].buffer.rule * is_fit
num_genes = self.main.rule.bit_width
genomes.append(self.main.rule)
self.main.rule = genome_crossover(
self.main, num_genes, *genomes,
mutation_prob=self.meta.mutation_prob
)
self.main.energy *= xmath.popc(self.main.rule) <= self.meta.max_genes
@color_effects.MovingAverage
def color(self):
"""Render cell's genome as hue/sat, cell's energy as value."""
if self._legacy_coloring:
red, green, blue = GenomeColor.modular(self.main.rule, 360)
else:
red, green, blue = GenomeColor.positional(self.main.rule,
self.main.rule.bit_width)
red = xmath.int(red * self.main.energy)
green = xmath.int(green * self.main.energy)
blue = xmath.int(blue * self.main.energy)
return (red, green, blue, )
class CrossbreedingExperiment(RegularExperiment):
"""Classic experiment for legacy EvoLife, where 'Bliambas' may form."""
word = "BANG! BANG! BANG! ON THE WALL FROM DUSK TILL DAWN"
seed_main1 = seeds.patterns.BigBang(
pos=(320, 180),
size=(100, 100),
vals={
"energy": RandInt(0, 1) * RandInt(0, 255),
"rule": LifeLike.golly2int("B35678/S5678"),
"rng": RandInt(0, 2 ** 16 - 1)
}
)
seed_main2 = seeds.patterns.BigBang(
pos=(0, 0),
size=(640, 200),
vals={
# you may use unary operators
"energy": +RandInt(0, 1) * RandInt(0, 255),
"rule": LifeLike.golly2int("B3/S23"),
# as well as reflected expressions
"rng": 0 + RandInt(0, 2 ** 16 - 1)
}
)
seed_main3 = seeds.patterns.BigBang(
pos=(0, 0),
size=(200, 360),
vals={
"energy": RandInt(0, 1) * RandInt(0, 255),
"rule": LifeLike.golly2int("B3/S23"),
"rng": RandInt(0, 2 ** 16 - 1)
}
)
seed_rng = seeds.patterns.PrimordialSoup(
vals={
"rng": RandInt(0, 2 ** 16 - 1)
}
)
# chain ordering matters, since areas are rewriting each other in order
seed = seed_rng + seed_main1 + seed_main2 + seed_main3
class CrossbreedingExperiment2(CrossbreedingExperiment):
"""Same crossbreeding experiment with different meta-parameters."""
death_speed = 23
max_genes = 11
mutation_prob = 0.001
if __name__ == "__main__":
run_simulation(EvoLife, CrossbreedingExperiment2)
You can’t perform that action at this time.