The Core (xentica.core)

Xentica core functionality is available via modules from this package.

In addition, you may use core package as a shortcut to the main classes of the framework.

The classes listed above are all you need to build CA models and experiments with Xentica, unless you are planning to implement custom core features like new lattices, borders, etc.

Base Classes (xentica.core.base)

The module with the base class for all CA models.

All Xentica models should be inherited from CellularAutomaton base class. Inside the model, you should correctly define the Topology class and describe the CA logic in emit(), absorb() and color() methods.

Topology is the place where you define the dimensionality, lattice, neighborhood and border effects for your CA. See xentica.core.topology package for details.

The logic of the model will follow Buffered State Cellular Automaton (BSCA) principle. In general, every cell mirrors its state in buffers by the number of neighbors, each buffer intended for one of the neighbors. Then, at each step, the interaction between cells is performed via buffers in 2-phase emit/absorb process. A more detailed description of the BSCA principle is available in The Core section of The Concept document.

emit() describes the logic of the first phase of BSCA. At this phase, you should fill the cell’s buffers with corresponding values, depending on the cell’s main state and (optionally) on neighbors’ main states. The easiest logic is to just copy the main state to buffers. It is especially useful when you’re intending to emulate classic CA (like Conway’s Life) with BSCA. Write access to the main state is prohibited there.

absorb() describes the logic of the second phase of BSCA. At this phase, you should set the cell’s main state, depending on neighbors’ buffered states. Write access to buffers is prohibited there.

color() describes how to calculate cell’s color from its raw state. See detailed instructions on it in xentica.core.color_effects.

The logic of the functions from above will be translated into C code at the moment of class instance creation. For the further instructions on how to use the cell’s main and buffered states, see xentica.core.properties, for the instructions on variables and expressions with them, see xentica.core.variables. Another helpful thing is meta-parameters, which are described in xentica.core.parameters.

A minimal example, the CA where each cell is taking the mean value of its neighbors each step:

from xentica import core
from xentica.core import color_effects

class MeanCA(core.CellularAutomaton):

    state = core.IntegerProperty(max_val=255)

    class Topology:
        dimensions = 2
        lattice = core.OrthogonalLattice()
        neighborhood = core.MooreNeighborhood()
        border = core.TorusBorder()

    def emit(self):
        for i in range(len(self.buffers)):
            self.buffers[i].state = self.main.state

    def absorb(self):
        s = core.IntegerVariable()
        for i in range(len(self.buffers)):
            s += self.neighbors[i].buffer.state
        self.main.state = s / len(self.buffers)

    @color_effects.MovingAverage
    def color(self):
        v = self.main.state
        return (v, v, v)
class xentica.core.base.BSCA

Bases: type

The meta-class for CellularAutomaton.

Prepares main, buffers and neighbors class variables being used in emit(), absorb() and color() methods.

mandatory_fields = ('dimensions', 'lattice', 'neighborhood', 'border')
class xentica.core.base.CellularAutomaton(experiment_class)

Bases: xentica.core.base.Translator

The base class for all Xentica models.

Generates GPU kernels source code, compiles them, initializes necessary GPU arrays and populates them with the seed.

After initialization, you can run a step-by-step simulation and render the field at any moment:

from xentica import core
import moire

class MyCA(core.CellularAutomaton):
    # ...

class MyExperiment(core.Experiment):
    # ...

ca = MyCA(MyExperiment)
ca.set_viewport((320, 200))

# run CA manually for 100 steps
for i in range(100):
    ca.step()
# render current timestep
frame = ca.render()

# or run the whole process interactively with Moire
gui = moire.GUI(runnable=ca)
gui.run()
Parameters:experiment_classExperiment instance, holding all necessary parameters for the field initialization.
apply_speed(dval)

Change the simulation speed.

Usable only in conduction with Moire, although you can use the speed value in your custom GUI too.

Parameters:dval – Delta by which speed is changed.
load(filename)

Load the CA state from filename file.

render()

Render the field at the current timestep.

You must call set_viewport() before do any rendering.

Returns:NumPy array of np.uint8 values, width * height * 3 size. The RGB values are consecutive.
save(filename)

Save the CA state into filename file.

set_viewport(size)

Set the viewport (camera) size and initialize GPU array for it.

Parameters:size – tuple with the width and height in pixels.
step()

Perform a single simulation step.

timestep attribute will hold the current step number.

toggle_pause()

Toggle paused flag.

When paused, the step() method does nothing.

class xentica.core.base.CachedNeighbor

Bases: object

The utility class, intended to hold the main and buffered CA state.

Experiments (xentica.core.experiment)

The collection of classes to describe experiments for CA models.

The experiment is a class with CA parameters stored as class variables. Different models may have a different set of parameters. To make sure all set correctly, you should inherit your experiments from Experiment class.

A quick example:

from xentica import core, seeds

class MyExperiment(core.Experiment):
    # RNG seed string
    word = "My Special String"
    # field size
    size = (640, 360, )
    # initial field zoom
    zoom = 3
    # initial field shift
    pos = [0, 0]
    # A pattern used in initial board state generation.
    # BigBang is a small area initialized with high-density random values.
    seed = seeds.patterns.BigBang(
        # position Big Bang area
        pos=(320, 180),
        # size of Big Bang area
        size=(100, 100),
        # algorithm to generate random values
        vals={
            "state": seeds.random.RandInt(0, 1),
        }
   )
class xentica.core.experiment.Experiment

Bases: object

The base class for all experiments.

Right now doing nothing, but will be improved in future versions. So it is advised to inherit your experiments from it.

Properties (xentica.core.properties)

The collection of classes to describe properties of CA models.

Warning

Do not confuse with Python properties.

Xentica properties are declaring as class variables and helping you to organize CA state into complex structures.

Each CellularAutomaton instance should have at least one property declared. The property name is up to you. If your model has just one value as a state (like in most classic CA), the best practice is to call it state as follows:

from xentica import core

class MyCA(core.CellularAutomaton):
    state = core.IntegerProperty(max_val=1)
    # ...

Then, you can use it in expressions of emit(), absorb() and color() functions as:

self.main.state
to get and set main state;
self.buffers[i].state
to get and set i-th buffered state;
self.neighbors[i].buffer.state
to get and set i-th neighbor buffered state.

Xentica will take care of all other things, like packing CA properties into binary representation and back, storing and getting corresponding values from VRAM, etc.

Most of properties will return DeferredExpression on access, so you can use them safely in mixed expressions:

self.buffers[i].state = self.main.state + 1
class xentica.core.properties.Property

Bases: xentica.core.expressions.DeferredExpression, xentica.core.mixins.BscaDetectorMixin

Base class for all properties.

It has a vast set of default functionality already in places. Though, you are free to re-define it all to implement really custom behavior.

best_type

Get the type that suits best to store a property.

Returns:tuple representing best type: (bit_width, numpy_dtype, gpu_c_type)
bit_width

Get the number of bits necessary to store a property.

Returns:Positive integer, a property’s bit width.
buf_num

Get a buffer’s index, associated to a property.

calc_bit_width()

Calculate the property’s bit width.

This is the method you most likely need to override. It will be called from bit_width().

Returns:Positive integer, the calculated property’s width in bits.
coords_declared

Test if the coordinates variables are declared.

ctype

Get the C type, based on the result of best_type().

Returns:C type that suits best to store a property.
declare_once()

Generate the C code to declare a variable holding a cell’s state.

You must push the generated code to the BSCA via self.bsca.append_code(), then declare necessary stuff via self.bsca.declare().

You should also take care of skipping the whole process if things are already declared.

declared

Test if the state variable is declared.

dtype

Get the NumPy dtype, based on the result of best_type().

Returns:NumPy dtype that suits best to store a property.
nbr_num

Get a neighbor’s index, associated to a property.

pack_cast(expr)

Fix given expression for safe use with packing.

Parameters:expr – Any C expression.
Returns:Fixed, correctly casted expression.
unpack_cast(expr)

Fix given expression for safe use with unpacking.

Parameters:expr – Any C expression.
Returns:Fixed, correctly casted expression.
width

Get the number of memory cells to store a property.

In example, if ctype == "int" and bit_width == 64, you need 2 memory cells.

Returns:Positive integer, a property’s width.
class xentica.core.properties.IntegerProperty(max_val)

Bases: xentica.core.properties.Property

Most generic property for you to use.

It is just a positive integer with the upper limit of max_val.

calc_bit_width()

Calculate the bit width, based on max_val.

class xentica.core.properties.ContainerProperty

Bases: xentica.core.properties.Property

A property acting as a holder for other properties.

Currently is used only for inner framework mechanics, in particular, to hold, pack and unpack all top-level properties.

It will be enhanced in future versions, and give you the ability to implement nested properties structures.

Warning

Right now, direct use of this class is prohibited.

buf_num

Get a buffer’s index, associated to a property.

calc_bit_width()

Calculate the bit width as a sum of inner properties’ bit widths.

declare_once()

Do all necessary declarations for inner properties.

Also, implements the case of the off-board neighbor access.

Parameters:init_val – The default value for the property.
deferred_write()

Pack the state and write its value to the VRAM.

CellularAutomaton calls this method at the end of the kernel processing.

nbr_num

Get a neighbor’s index, associated to a property.

properties

Get inner properties.

unpacked

Test if inner properties are unpacked from the memory.

values()

Iterate over properties, emulating dict functionality.

Parameters (xentica.core.parameters)

The collection of classes to describe the model’s meta-parameters.

The parameter is the value that influences the whole model’s behavior in some way. Each parameter has a default value. Then you could customize them per each experiment or change interactively using Bridge.

There are two types of parameters in Xentica:

Non-interactive
are constant during a single experiment run. The change of this parameter is impossible without a whole model’s source code being rebuilt. The engine makes sure those params are correctly defined globally with the #define directive. So actually, even if you’ll change their values at runtime, it doesn’t affect the model in any way.
Interactive
could be effectively changed at runtime, since engine traits them as extra arguments to CUDA kernels. That means, as long as you’ll set a new value to an interactive parameter, it will be passed into the kernel(s) at the next timestep. Be warned though: every parameter declared as interactive, will degrade the model’s performance further.

The example of parameters’ usage:

from xentica import core
from xentica.tools.rules import LifeLike
from examples.game_of_life import GameOfLife, GOLExperiment


class LifelikeCA(GameOfLife):
    rule = core.Parameter(
        default=LifeLike.golly2int("B3/S23"),
        interactive=True,
    )

    def absorb(self):
        # parent's clone with parameter instead of hardcoded rule
        neighbors_alive = core.IntegerVariable()
        for i in range(len(self.buffers)):
            neighbors_alive += self.neighbors[i].buffer.state
        is_born = (self.rule >> neighbors_alive) & 1
        is_sustain = (self.rule >> 9 >> neighbors_alive) & 1
        self.main.state = is_born | is_sustain & self.main.state


class DiamoebaExperiment(GOLExperiment):
    rule = LifeLike.golly2int("B35678/S5678")


model = LifelikeCA(DiamoebaExperiment)
class xentica.core.parameters.Parameter(default=0, interactive=False)

Bases: xentica.core.mixins.BscaDetectorMixin

The implementation of Xentica meta-parameter.

Parameters:
  • default – The default value for the parameter to use when it’s omitted in the experiment class.
  • interactiveTrue if the parameter could be safely changed at runtime (more details above in the module description).
ctype

Get the parameter’s C type.

dtype

Get the parameter’s NumPy type.

name

Get the parameter’s name.

value

Get the parameter’s value directly.

Variables (xentica.core.variables)

The collection of classes to declare and use C variables and constants.

If the logic of your emit(), absorb() or color() functions requires the intermediate variables, you must declare them via classes from this module in the following way:

from xentica import core

class MyCA(core.CellularAutomaton):
    # ...

    def emit(self):
        myvar = core.IntegerVariable()

Then you can use them in mixed expressions, like this:

myvar += self.neighbors[i].buffer.state
self.main.state = myvar & 1

You may also define constants or other #define patterns with Constant class.

class xentica.core.variables.Constant(name, value)

Bases: xentica.core.mixins.BscaDetectorMixin

The class for defining constants and #define patterns.

Once you instantiate Constant, you must feed it to CellularAutomaton.define_constant() in order to generate the correct C code:

const = Constant("C_NAME", "some_value")
self.bsca.define_constant(const)
Parameters:
  • name – The name to use in #define.
  • value – A value for the define, it will be converted to a string with str().
get_define_code()

Get the C code for #define.

name

Get the name of the constant.

class xentica.core.variables.Variable(val=None, name='var')

Bases: xentica.core.expressions.DeferredExpression, xentica.core.mixins.BscaDetectorMixin

The base class for all variables.

Most of the functionality for variables is already implemented in it. Though, you are free to re-define it all to implement really custom behavior.

Parameters:
  • val – The initial value for the variable.
  • name – Fallback name to declare the variable with.
code

Get the variable name as a C code.

declare_once()

Declare the variable and assign the initial value to it.

var_name

Get the variable name.

class xentica.core.variables.IntegerVariable(val='0', **kwargs)

Bases: xentica.core.variables.Variable

The variable intended to hold a positive integer.

var_type = 'unsigned int'

C type to use in definition.

class xentica.core.variables.FloatVariable(val='0.0f', **kwargs)

Bases: xentica.core.variables.Variable

The variable intended to hold a 32-bit float.

var_type = 'float'

C type to use in definition.

Expressions (xentica.core.expressions)

The module holding base expressions classes.

class xentica.core.expressions.DeferredExpression(code='')

Bases: object

The base class for other classes intended to be used in mixed expressions.

In particular, it is used in base Variable and Property classes.

Most of the magic methods dealing with binary and unary operators as well as augmented assigns are automatically overridden for this class. As a result, you can use its subclasses in mixed expressions with ordinary Python values. See the example in variables module description.

Allowed binary ops
+, -, *, /, %, >>, <<, &, ^, |, <, <=, >, >=, ==, !=
Allowed unary ops
+, -, ~, abs, int, float, round
Allowed augmented assigns
+=, -=, *=, /=, %=, <<=, >>=, &=, ^=, |=

Color Effects (xentica.core.color_effects)

The collection of decorators for the color() method, each CA model should have.

The method should be decorated by one of the classes below, otherwise the correct model behavior will not be guaranteed.

All decorators are get the (red, green, blue) tuple from color() method, then process it to create some color effect.

A minimal example:

from xentica import core
from xentica.core import color_effects

class MyCA(core.CellularAutomaton):

    state = core.IntegerProperty(max_val=1)

    # ...

    @color_effects.MovingAverage
    def color(self):
        red = self.main.state * 255
        green = self.main.state * 255
        blue = self.main.state * 255
        return (red, green, blue)
class xentica.core.color_effects.ColorEffect(func)

Bases: xentica.core.mixins.BscaDetectorMixin

The base class for other color effects.

You may also use it as a standalone color effect decorator, it just doing nothing, storing the calculated RGB value directly.

To create your own class inherited from ColorEffect, you should override __call__ method, and place a code of the color processing into self.effect. The code should process values of new_r, new_g, new_b variables and store the result back to them.

An example:

class MyEffect(ColorEffect):

    def __call__(self, *args):
        self.effect = "new_r += 20;"
        self.effect += "new_g += 15;"
        self.effect += "new_b += 10;"
        return super().__call__(*args)
class xentica.core.color_effects.MovingAverage(func)

Bases: xentica.core.color_effects.ColorEffect

Apply the moving average to each color channel separately.

With this effect, 3 additional settings are available for you in Experiment classes:

fade_in
The maximum delta by which a channel could increase its value in a single timestep.
fade_out
The maximum delta by which a channel could decrease its value in a single timestep.
smooth_factor
The divisor for two previous settings, to make the effect even smoother.

Renderers (xentica.core.renderers)

The collection of classes implementing render logic.

The renderer takes the array of cells’ colors and renders the screen frame from it. Also, it is possible to expand a list of user actions, adding ones specific to the renderer, like zoom, scroll, etc.

The default renderer is RendererPlain. Though there are no other renderers yet, you may try to implement your own and apply it to CA model as follows:

from xentica.core import CellularAutomaton
from xentica.core.renderers import Renderer

class MyRenderer(Renderer):
    # ...

class MyCA(CellularAutomaton):
    renderer = MyRenderer()
    # ...
class xentica.core.renderers.Renderer

Bases: xentica.core.mixins.BscaDetectorMixin

Base class for all renderers.

For correct behavior, renderer classes should be inherited from this class. Then at least render_code() method should be implemented.

However, if you are planning to add user actions specific to your renderer, more methods should be overridden:

  • __init__(), where you expand a list of kernel arguments in self.args;
  • get_args_vals(), where you expand the list of arguments’ values;
  • setup_actions(), where you expand a dictionary of bridge actions;

See RendererPlain code as an example.

get_args_vals(bsca)

Get a list of kernel arguments’ values.

The order should correspond to self.args, with the values themselves as either PyCUDA GpuArray or correct NumPy instance. Those values will be used directly as arguments to PyCUDA kernel execution.

Parameters:bscaxentica.core.CellularAutomaton instance.
render_code()

Generate C code for rendering.

At a minimum, it should process cells colors stored in col GPU-array, and store the resulting pixel’s value into img GPU-array. It can additionally use other custom arguments if any of them set up.

setup_actions(bridge)

Expand bridge with custom user actions.

You can do it as follows:

class MyRenderer(Renderer):
    # ...

    @staticmethod
    def my_awesome_action():
        def func(ca, gui):
            # do something with ``ca`` and ``gui``
        return func

    def setup_actions(self):
        bridge.key_actions.update({
            "some_key": self.my_awesome_action(),
        })
Parameters:bridgexentica.bridge.Bridge instance.
class xentica.core.renderers.RendererPlain(projection_axes=None)

Bases: xentica.core.renderers.Renderer

Render board as 2D plain.

If your model has more than 2 dimensions, a projection over projection_axes tuple will be made. The default is two first axes, which corresponds to (0, 1) tuple.

static apply_move(bsca, *args)

Apply a move action to CA class.

Parameters:bscaxentica.core.CellularAutomaton instance.
static apply_zoom(bsca, dval)

Apply a zoom action to CA class.

Parameters:
  • bscaxentica.core.CellularAutomaton instance.
  • dval – Delta by which the field is zoomed.
get_args_vals(bsca)

Extend kernel arguments values.

static move(delta_x, delta_y)

Move over a game field by some delta.

Parameters:
  • dx – Delta by x-axis.
  • dy – Delta by y-axis.
render_code()

Implement the code for the render kernel.

setup_actions(bridge)

Extend the bridge with the scroll and zoom user actions.

static zoom(dzoom)

Zoom a game field by some delta.

Parameters:dzoom – Delta by which field is zoomed.

Exceptions (xentica.core.exceptions)

The collection of exceptions, specific to the framework.

exception xentica.core.exceptions.XenticaException

Bases: Exception

The basic Xentica framework exception.

Mixins (xentica.core.mixins)

The collection of mixins to be used in core classes.

Would be interesting only if you are planning to hack into Xentica core functionality.

class xentica.core.mixins.BscaDetectorMixin

Bases: object

Add a functionality to detect CellularAutomaton class instances holding current class.

bsca

Get a CellularAutomaton instance holding current class.

The objects tree is scanned up to the top and the first instance found is returned.

class xentica.core.mixins.DimensionsMixin

Bases: object

The base functionality for classes, operating on a number of dimensions.

Adds dimensions property to a class, and checks it automatically over a list of allowed dimensions.

allowed_dimension(num_dim)

Test if a particular dimensionality is allowed.

Parameters:num_dim – The number of dimensions to test.
Returns:Boolean value, either dimensionality is allowed or not.
dimensions

Get a number of dimensions.

supported_dimensions = []

A list of integers, containing a supported dimensionality. You must set it manually for every class using DimensionsMixin.