class GameOfLifeColor(GameOfLife): """ Game Of Life variant with RGB color. This is an example of how to use multiple properties per cell. """ state = core.IntegerProperty(max_val=1) red = core.IntegerProperty(max_val=255) green = core.IntegerProperty(max_val=255) blue = core.IntegerProperty(max_val=255) def emit(self): """Copy all properties to surrounding buffers.""" for i in range(len(self.buffers)): self.buffers[i].state = self.main.state self.buffers[i].red = self.main.red self.buffers[i].green = self.main.green self.buffers[i].blue = self.main.blue def absorb(self): """ Calculate RGB as neighbors sum for living cell only. Note, parent ``absorb`` method should be called using direct class access, not via ``super``. """ GameOfLife.absorb(self) red_sum = core.IntegerVariable() green_sum = core.IntegerVariable() blue_sum = core.IntegerVariable() for i in range(len(self.buffers)): red_sum += self.neighbors[i].buffer.red + 1 green_sum += self.neighbors[i].buffer.green + 1 blue_sum += self.neighbors[i].buffer.blue + 1 self.main.red = red_sum * self.main.state self.main.green = green_sum * self.main.state self.main.blue = blue_sum * self.main.state @color_effects.MovingAverage def color(self): """Calculate color as usual.""" red = self.main.state * self.main.red green = self.main.state * self.main.green blue = self.main.state * self.main.blue return (red, green, blue)
class InvertCA(core.CellularAutomaton): """CA that inverts each cell's value each step.""" state = core.IntegerProperty(max_val=1) # vars should be declared at the class level, # in order to use them in direct assigns intvar = IntegerVariable() class Topology: """Most common topology.""" dimensions = 2 lattice = core.OrthogonalLattice() neighborhood = core.MooreNeighborhood() border = core.TorusBorder() def emit(self): """Do nothing at emit phase.""" self.intvar = self.main.state self.buffers[0].state = self.intvar def absorb(self): """Invert cell's value.""" self.intvar = self.buffers[0].state self.main.state = -self.intvar var_list = [ IntegerVariable(name="self_var"), IntegerVariable(), ] var_list[1] += 1 def color(self): """Do nothing, no color processing required."""
class ShiftingSands(RegularCA): """ CA for non-uniform buffer interactions test. It emits the whole value to a constant direction, then absorbs surrounding values by summing them. """ state = core.IntegerProperty(max_val=1) def emit(self): """Emit the whole value to a constant direction.""" direction = 0 for i in range(len(self.buffers)): if i == direction: self.buffers[i].state = self.main.state else: self.buffers[i].state = 0 def absorb(self): """Absorb surrounding values by summing them.""" new_val = core.IntegerVariable() for i in range(len(self.buffers)): new_val += self.neighbors[i].buffer.state self.main.state = new_val @color_effects.MovingAverage def color(self): """Render contrast black & white cells.""" red = self.main.state * 255 green = self.main.state * 255 blue = self.main.state * 255 return (red, green, blue)
class PoissonWalk(core.CellularAutomaton): """ CA with Poisson process per each cell. More energy cell has, more likely it will transfer energy to the neighbour cell. """ energy = core.IntegerProperty(max_val=2 ** 16 - 1) gate = core.IntegerProperty(max_val=2 ** 3 - 1) interval = core.IntegerProperty(max_val=2 ** 12 - 1) rng = core.RandomProperty() default_denergy = core.Parameter(default=1) class Topology: """Standard Moore neighbourhood with torus topology.""" dimensions = 2 lattice = core.OrthogonalLattice() neighborhood = core.MooreNeighborhood() border = core.TorusBorder() def emit(self): """ Implement the logic of emit phase. Each cell is a Poisson process that fires an energy in the gate direction, when Poisson event occurs. The rate of events depends on cell's energy level: more energy, higher the rate. The amount of energy depends on the cell's own "valency" and its gated neighbour's "valency" (energy % 8). When neighbour's valency is 0, cell spreads the amount of energy, equal to its own valency. Otherwise, it spreads some default amount of energy. Cells are also spreading their gate values when event occurs, so they are "syncing" with neighbours. """ shade = xmath.min(255, self.main.energy) lmb = xmath.float(self.main.interval) / xmath.float(256 - shade) prob = 1 - xmath.exp(-lmb) fired = core.IntegerVariable() fired += xmath.int(self.main.rng.uniform < prob) fired *= self.main.energy > 0 denergy = xmath.min(self.default_denergy, shade) gate = self.main.gate mutated = core.IntegerVariable() mutated += xmath.int(self.main.rng.uniform < 0.0001) denergy = denergy - 1 * (denergy > 0) * mutated energy_passed = core.IntegerVariable() for i in range(len(self.buffers)): valency1 = self.neighbors[i].main.energy % 8 valency2 = self.main.energy % 8 gate_fit = (i == gate) full_transition = denergy * (valency1 != 0 | valency2 == 0) potent_transition = valency2 * (valency1 == 0) denergy_fin = full_transition + potent_transition energy_passed += denergy_fin * fired * gate_fit self.buffers[i].energy = energy_passed * fired * gate_fit self.buffers[i].gate = (self.main.gate + 7) * fired * gate_fit self.main.interval = (self.main.interval + 1) * (energy_passed == 0) self.main.energy -= energy_passed * (energy_passed > 0) self.main.gate = (self.main.gate + 1) % 8 def absorb(self): """ Implement the logic of absorb phase. Each cell just sums incoming energy and gate values. """ incoming_energy = core.IntegerVariable() incoming_gate = core.IntegerVariable() for i in range(len(self.buffers)): incoming_energy += self.neighbors[i].buffer.energy incoming_gate += self.neighbors[i].buffer.gate self.main.energy += incoming_energy self.main.gate += incoming_gate % 8 @color_effects.MovingAverage def color(self): """ Implement the logic of cell's color calculation. Here, we highlight cells with valency != 0. Color depends on valency value (energy % 8). """ energy = xmath.min(255, self.main.energy) vacuum = (energy % 8 == 0) * energy red = (((energy % 8) >> 2) & 1) * 255 + vacuum blue = (((energy % 8) >> 1) & 1) * 255 + vacuum green = ((energy % 8) & 1) * 255 + vacuum return (red, green, blue)
class GameOfLife(core.CellularAutomaton): """ The classic CA built with Xentica framework. It has only one property called ``state``, which is positive integer with max value of 1. """ state = core.IntegerProperty(max_val=1) class Topology: """ Mandatory class for all ``CellularAutomaton`` instances. All class variables below are also mandatory. Here, we declare the topology as a 2-dimensional orthogonal lattice with Moore neighborhood, wrapped to a 3-torus. """ dimensions = 2 lattice = core.OrthogonalLattice() neighborhood = core.MooreNeighborhood() border = core.TorusBorder() def emit(self): """ Implement the logic of emit phase. Statements below will be translated into C code as emit kernel at the moment of class creation. Here, we just copy main state to surrounding buffers. """ for i in range(len(self.buffers)): self.buffers[i].state = self.main.state def absorb(self): """ Implement the logic of absorb phase. Statements below will be translated into C code as well. Here, we sum all neigbors buffered states and apply Conway rule to modify cell's own state. """ neighbors_alive = core.IntegerVariable() for i in range(len(self.buffers)): neighbors_alive += self.neighbors[i].buffer.state is_born = (8 >> neighbors_alive) & 1 is_sustain = (12 >> neighbors_alive) & 1 self.main.state = is_born | is_sustain & self.main.state @color_effects.MovingAverage def color(self): """ Implement the logic of cell's color calculation. Must return a tuple of RGB values computed from ``self.main`` properties. Also, must be decorated by a class from ``color_effects`` module. Here, we simply define 0 state as pure black, and 1 state as pure white. """ red = self.main.state * 255 green = self.main.state * 255 blue = self.main.state * 255 return (red, green, blue)
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; - 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 * 2) 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=True): """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)): nbr_energy = self.neighbors[i].buffer.energy nbr_rule = self.neighbors[i].buffer.rule num_neighbors += xmath.min(1, (nbr_energy + nbr_rule)) is_sustained = core.IntegerVariable() 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)): nbr_energy = self.neighbors[i].buffer.energy nbr_rule = self.neighbors[i].buffer.rule is_alive = xmath.min(1, (nbr_energy + nbr_rule)) 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) # 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 old_rule = core.IntegerVariable() old_rule += self.main.rule old_energy = core.IntegerVariable() old_energy += self.main.energy new_genome = genome_crossover( self.main, num_genes, *genomes, max_genes=self.meta.max_genes, mutation_prob=self.meta.mutation_prob ) self.main.rule = new_genome + self.main.rule * (new_genome == 0) # new energy value self.main.energy *= 0 is_live = core.IntegerVariable() old_live = (old_energy + old_rule) == 0 is_live += (old_energy < 0xff) & (old_live | is_sustained) old_dead = (old_energy + old_rule) != 0 new_energy = old_energy + self.meta.death_speed * old_dead self.main.energy = new_energy * (self.main.rule == old_rule) + \ self.main.energy * (self.main.rule != old_rule) self.main.rule = old_rule * (self.main.rule == old_rule) + \ self.main.rule * (self.main.rule != old_rule) self.main.energy *= is_live self.main.rule *= is_live @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 >> 1, 360) else: red, green, blue = GenomeColor.positional(self.main.rule, self.main.rule.bit_width) is_live = (self.main.rule > 0) * (self.main.energy < 255) energy = (255 - self.main.energy) * is_live red = xmath.int(red * energy) green = xmath.int(green * energy) blue = xmath.int(blue * energy) return (red, green, blue, )
class ConservedLife(RegularCA): """ Energy conserved model, acting on Life-like rules. Main rules are: - Each cell has an integer energy level; - Cell is 'full' when its energy level is greater or equal to some treshold value, or it's 'empty' otherwise; - Full cell must spread a part of its energy, that is over the treshold value, or when it's 'dying'; - Empty cell must spread a part of its energy if it's not 'birthing'; - Cell is 'dying' if number of 'full' neighbors is not in 'sustain' ruleset; - Cell is 'birthing' if number of 'full' neighbors is in 'birth' ruleset; - When spreading an energy, cell firstly chooses 'birthing' neighbors as targets, then 'empty' neighbors; - If there are several equal targets to spread an energy, cell is choosing the one in 'stochastic' (PRNG) way, keeping the whole automaton deterministic. Additional rules for evolutionary setting: - Each cell has a Life-like rule encoded into its genome; - If cell is 'empty', its genome is calculated as a crossover between all neighbors, that passed an energy to it; - Each cell is operating over its own rule (genome), when calculating dying/birthing status. """ energy = core.IntegerProperty(max_val=2**14 - 1) new_energy = core.IntegerProperty(max_val=2**14 - 1) birthing = core.IntegerProperty(max_val=1) rule = core.TotalisticRuleProperty(outer=True) rng = core.RandomProperty() death_speed = core.Parameter(default=1) full_treshold = core.Parameter(default=1) max_genes = core.Parameter(default=9) mutation_prob = core.Parameter(default=.0) def __init__(self, *args, legacy_coloring=True): """Support legacy coloring as needed.""" self._legacy_coloring = legacy_coloring super().__init__(*args) def emit(self): """Apply ConcervedLife dynamics.""" self.main.new_energy = 1 * self.main.energy # calculate number of full neighbors num_full = core.IntegerVariable() for i in range(len(self.buffers)): is_full = self.neighbors[i].main.energy >= self.meta.full_treshold num_full += is_full # decide, do cell need to spread an energy me_full = self.main.energy >= self.meta.full_treshold me_empty = self.main.energy < self.meta.full_treshold me_dying = xmath.int(self.main.rule.is_sustained(num_full)) == 0 me_dying = xmath.int(me_dying) me_birthing = self.main.rule.is_born(num_full) has_free_energy = self.main.energy > self.meta.full_treshold has_free_energy = xmath.int(has_free_energy) need_spread_full = 1 * me_full * (has_free_energy | me_dying) need_spread_empty = 1 * me_empty * (xmath.int(me_birthing) == 0) need_spread = need_spread_full + need_spread_empty # search the direction to spread energy denergy = core.IntegerVariable() energy_passed = core.IntegerVariable() energy_passed *= 1 gate = core.IntegerVariable() gate += xmath.int(self.main.rng.uniform * 8) gate_final = core.IntegerVariable() treshold = self.meta.full_treshold for i in range(len(self.buffers) * 3): i_valid = (i >= gate) * (i < gate + len(self.buffers) * 2) is_birthing = self.neighbors[i % 8].main.birthing * (i < 8 + gate) is_empty = self.neighbors[i % 8].main.energy < treshold is_empty = is_empty * (i >= 8 + gate) is_fit = is_empty + is_birthing denergy *= 0 denergy += xmath.min(self.main.new_energy, self.meta.death_speed) denergy *= need_spread * is_fit * (energy_passed == 0) denergy *= i_valid gate_final *= (energy_passed != 0) gate_final += (i % 8) * (energy_passed == 0) energy_passed += denergy # spread the energy in chosen direction for i in range(len(self.buffers)): gate_fit = i == gate_final self.buffers[i].energy = energy_passed * gate_fit self.buffers[i].rule = self.main.rule * gate_fit self.main.new_energy -= energy_passed def absorb(self): """Absorb an energy from neighbors.""" # absorb incoming energy and calculate 'birthing' status incoming_energy = core.IntegerVariable() num_full = core.IntegerVariable() treshold = self.meta.full_treshold for i in range(len(self.buffers)): incoming_energy += self.neighbors[i].buffer.energy is_full = self.neighbors[i].main.new_energy >= treshold num_full += is_full self.main.energy = self.main.new_energy + incoming_energy self.main.birthing = self.main.rule.is_born(num_full) self.main.birthing *= self.main.energy < self.meta.full_treshold # 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.energy > 0 is_fit = is_fit * (self.neighbors[i].buffer.rule > 0) genomes[i] += self.neighbors[i].buffer.rule * is_fit num_genes = self.main.rule.bit_width new_genome = genome_crossover( self.main, num_genes, *genomes, max_genes=self.meta.max_genes, mutation_prob=self.meta.mutation_prob) * (self.main.energy < self.meta.full_treshold) self.main.rule = new_genome + self.main.rule * (new_genome == 0) @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 >> 1, 360) else: red, green, blue = GenomeColor.positional(self.main.rule, self.main.rule.bit_width) shade = xmath.min(self.main.energy, self.meta.full_treshold) shade = shade * 255 / self.meta.full_treshold red = xmath.int(red * shade) green = xmath.int(green * shade) blue = xmath.int(blue * shade) return ( red, green, blue, )