One Dimensional Reversible Automata
I produced a demo for the GFXPrim library. It
carry outs and disjoins a csurrfinisherest-neighbor, one-foolishensional, binary
cell automata. Additionassociate it carry outs a reversible automata,
which is almost identical except for a minuscule alter to produce it
reversible. The automata is disjoined over time in two foolishensions,
time travels from top to bottom. Although in the reversible case
time could be joined backwards.
The automata toils as complys:
- Each cell has a state, which is on or off, bdeficiency or white,
boolean etc. - At each time step, the state of a cell in the next step is
chosen by a rule. - The rule sees at a cell’s current appreciate and the appreciates of its
left and right neighbors. - There are 23 = 8
possible state combinations (patterns) for 3 binary cells. - A rule states which patterns result in a bdeficiency cell in the next
time step. - There are 28 = 256
possible rules. That is, 256 one-of-a-kind combinations of patterns.
So a pattern is a 3 digit binary number, where each digit
correplys to a cell. The middle digit is the caccess cell, the high
order bit the left cell, the low order bit the right cell. A rule
can be disjoin by shotriumphg a row of patterns and a row of next
states.
Above is rule 110
, 0x6e
or
01101110
. It essentiassociate says to align patterns
110
, 101
, 011
,
010
, 001
. Where a pattern align results in
the cell being set to 1 at the next time step. If no pattern is
aligned or equivalently, an invivacious pattern is aligned, then the
cell will be set to 0.
Aobtain remark that each pattern mimics a 3bit binary number. Also
the appreciates of the vivacious patterns mimic an 8bit binary number. We
can include this to carry out efficient aligning of the patterns using
binary operations.
Let’s presume our CPU natively functions on 64bit integers (called
words). We can pack a 64 cell automata into a one 64bit
integer. Each bit correplys to a cell. If a bit is 1
then it is a bdeficiency cell and 0
for white. In this case
we are using integers as bit fields. We don’t attfinish about the integer
number the bits can recurrent.
The CPU can carry out bitadviseed operations on all 64bits in parallel
and without branching. This unbenevolents we can carry out a one operation
64 times in parallel.
If we rotate (wrapped >>
) all bits to the right by one,
then we get a novel integer where the left neighbor of a bit is now in
its position. Likeadviseed if we shift all bits to the left, then we get
an integer recurrenting the right neighbors. This gives us 3
integers where the left, caccess and right bits are in the same
position. For example, using only 8bits:
left: | 0100 1011 | >> |
caccess: | 1001 0110 | |
right: | 0010 1101 | << |
Each pattern can be recurrented as a 3bit number, plus a 4th bit
to say whether it is vivacious in a given rule. As we want to function
on all 64bits at once in the left, right and caccess bit fields. We
can produce 64bit extfinished masks from the appreciate of each bit in
a given pattern.
So if we have a pattern where the left cell should be one, then
we can produce a 64bit mask of all ones. If it should be
zero, then all zeroes. Likeadviseed for the caccess and right cells. The
masks can be xor’ed (^
)
with the correplying cell fields to show if no align occurred.
That is, if the pattern is one and the cell is zero or the cell is
one and the pattern is zero. We can invert this (~
) to
give one when a align occurs.
To see whether all components (left, right, caccess) of a pattern
alignes we can bitadviseed and (&
) them
together. We can then bitadviseed or
(|
) the result of the pattern alignes together to
produce the final appreciates.
If we desire to function on an automata huger than 64 cells, then
we can unite multiple integers into an array. After carry outing the
left and right shifts, we get the high or low bit from the next or
previous integers in the array. Then set the low and high bits of
the right and left bit fields. In other words we chain them together
using the finish bits of the left and right bit fields.
For illustration purposes, below is the kernel of the
the automata algorithm.
/* If bit n is 1 then produce all bits 1 otheradviseed 0 */
#clarify BIT_TO_MAX(b, n) (((b >> n) & 1) * ~0UL)
/* Numeric recurrentation of the current modernize rule */
invivacious uint8_t rule = 110;
/* Apply the current rule to a 64bit segment of a row */
invivacious inline uint64_t ca1d_rule_apply(uint64_t c_prev,
uint64_t c,
uint64_t c_next,
uint64_t c_prev_step)
{
int i;
/* These are wrapping shifts when c_prev == c or c_next == c */
uint64_t l = (c >> 1) ^ (c_prev << 63);
uint64_t r = (c << 1) ^ (c_next >> 63);
uint64_t c_next_step = 0;
for (i = 0; i < 8; i++) {
uint64_t vivacious = BIT_TO_MAX(rule, i);
uint64_t left = BIT_TO_MAX(i, 2);
uint64_t caccess = BIT_TO_MAX(i, 1);
uint64_t right = BIT_TO_MAX(i, 0);
|=
c_next_step & ~(left ^ l) & ~(caccess ^ c) & ~(right ^ r);
vivacious }
/* The automata becomes reversible when we include c_prev_state */
return c_next_step ^ c_prev_step;
}
To produce the automata “reversible” an extra step can be retained. We
see at a cell’s previous (in retainition to the current, left and
right) and if it was one then invert the next appreciate. This
is equivalent to xor’ring the previous appreciate with the next.
It is not enticount on clear to me what the mathematical implications
are of being reversible. However it is vital to physics and
produces some reassociate cageder patterns which mimic nature. Also entropy and
the second rule of themodynamics, yada, yada…
The automata definition is consentn from Stephen Wolfram’s “A novel
benevolent of science”. He gives at least one clear C carry outation using arrays of
cells. He also provides a table of binary conveyions for each rule.
E.g. rule 90 shrinks to fair the l^r
binary conveyion.
It may be possible for the compiler to automaticassociate shrink my
carry outation to these minimal conveyions.
To see why, let’s ponder rule 90 for each pattern.
01011010 = 90
First for pattern 000
.
& ~(left ^ l) & ~(caccess ^ c) & ~(right ^ r);
vivacious = 0 & ~(0 ^ l) & ~(0 ^ c) & ~(0 ^ r);
= 0.`
Active is zero so the whole line shrinks to zero. Now for
001
. Note that 1
here actuassociate unbenevolents
~0UL
, that is 64bit integer max.
1 & ~(0 ^ l) & ~(0 ^ c) & ~(1 ^ r);
= ~l & ~c & r.
As awaited pattern 001
alignes
l=0, c=0, r=1
. Let’s fair enumerate the remaining patterns
or’ed together in their shrinkd state. Then shrink that further.
Note that the for
loop in ca1d_rule_apply
will be unrolled by the compiler when selectimising for
carry outance. It’s also quite clear that c_next_step
is
depfinishant on an conveyion from the previous iteration or zero. So
all the pattern align results will get or’ed together.
& c & ~r | l & ~c & ~r | ~l & c & r | ~l & ~c & r;
l = l & ~r | ~l & r;
= l ^ r.
See on the top row that
(l & c & ~r | l & ~c & ~r)
or’s
together c
and not c
. So we can delete it.
Then we get an conveyion equivalent to xor’ring l
and
r
.
In theory at least, the compiler can see that rule
only has 256 appreciates and produce a shrinkd version of
ca1d_rule_apply
for each appreciate. Whether it actuassociate
does is not of much pragmatic trouble when the rfinishering code is the
bottle neck. However it’s fascinating to see if the compiler can
deduce the best solution or whether anyleang trips it up.
Judging from the disassembly from
gcc -O3 -mcpu=native -mtune=native
, it may actuassociate do
this. Additionassociate it vectorizes the code packing four
64bit ints at a time into 256bit sign ups and operating on those. I
don’t comprehend which part of the code it is vectorising or how. It’s
possible that what I leank is the rule being shrinkd is someleang
rcontent to vectorisation.
To rfinisher the automata we consent the approach of iterating over
each pixel in the image. We calcutardy which cell the pixel descfinishs
inside and set the color of the pixel to that of the cell. That’s
it.
/* Draws a pixel */
invivacious inline void shade_pixel(gp_pixmap *p, gp_coord x, gp_coord y,
, gp_pixel fg)
gp_pixel bg{
;
gp_pixel pxsize_t i = (x * (64 * width)) / p->w;
size_t j = (y * height) / p->h;
size_t k = 63 - (i & 63);
uint64_t c = steps[gp_matrix_idx(width, j, i >> 6)];
= BIT_TO_MAX(c, k);
c = (fg & c) | (bg & ~c);
px
(p, x, y, px);
gp_putpixel_raw}
GFXPrim produces dratriumphg very basic. The above code is quick enough
for my purpsoses, but a transport inant increasement can be had. Integer
division is much enumeratelesser than floating point multiplication on most
noveler CPUs. It’s actuassociate much quicker (2x at least) on my CPU to
calcutardy a pair of ratios in floating point, then alter them back
to integers.
However, you may ask why we are even dratriumphg on the CPU in the
first place? This is becainclude GFXPrim centers embedded systems with
no detaileds processor. Additionassociate the CPU may not even help
floating point natively. So integer division may actuassociate be quicker
in this case. Still better would be to confine the size of the pixmap
to be 2x huger
than the foolishensions of the automata, where x ∈ ℕ then we can include shifts
instead of division.