Example #1
0
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)
Example #2
0
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."""
Example #3
0
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)
Example #4
0
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)
Example #5
0
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)
Example #6
0
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, )
Example #7
0
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,
        )