Generating rows of a rule 30 cellular automaton - python

Rule 30 is a one dimensional cellular automaton where only the cells in the previous generation are considered by the current generation. There are two states that a cell can be in: 1 or 0. The rules for creating the next generation are represented in the row below, and depend on the cell immediately above the current cell, as well as it's immediate neighbours.
The cellular automaton is applied by the following rule (using bitwise operators):
left_cell ^ (central_cell | right_cell)
This rule forms the table below:
Now I tried to implement these rules into Python, using numpy. I defined an initial state that accepts width as a parameter and produces an initial row of zeros with 1 in the middle.
def initial_state(width):
initial = np.zeros((1, width), dtype=int)
if width % 2 == 0:
initial = np.insert(initial, int(width / 2), values=0, axis=1)
initial[0, int(width / 2)] = 1
return initial
else:
initial[0, int(width / 2)] = 1
return initial
The function below just produces the second generation given an initial row. How do I create a for loop that keeps producing new generations until the first element of the last bottom row becomes 1?
def rule30(array):
row1 = np.pad(array,[(0,0), (1,1)], mode='constant')
next_row = array.copy()
for x in range(1, array.shape[0]+1):
for y in range(1, array.shape[1]+1):
if row1[x-1][y-1] == 1 ^ (row1[x-1][y] == 1 or row1[x-1][y+1] == 1):
next_row[x - 1, y - 1] = 1
else:
next_row[x - 1, y - 1] = 0
return np.concatenate((array, next_row))
For example, if the input is
A = [0, 0, 0, 1, 0, 0, 0]
The output should be
>>> print(rule30(A))
[[0, 0, 0, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 1, 1, 0, 0, 1, 0],
[1, 1, 0, 1, 1, 1, 1]]

Here is the code based on string representations and lookup. It does use some of the ideas from the comments above. Besides I added padding for handling edge cells - the conditions were unclear about that. Also note that your proposed patterns table is not symmetric. Compare new states for '110' and '011'.
def rule30(a):
patterns = {'111': '0', '110': '0', '101': '0', '100': '1',
'011': '1', '010': '1', '001': '1', '000': '0', }
a = '0' + a + '0' # padding
return ''.join([patterns[a[i:i+3]] for i in range(len(a)-2)])
a = '0001000'
result = [list(map (int, a))]
while a[0] != '1':
a = rule30(a)
result.append (list(map (int, a)))
print (result) # list of lists
print (np.array(result)) # np.array
list of lists:
[[0, 0, 0, 1, 0, 0, 0], [0, 0, 1, 1, 1, 0, 0], [0, 1, 1, 0, 0, 1, 0], [1, 1, 0, 1, 1, 1, 1]]
np.array:
array([[0, 0, 0, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 1, 1, 0, 0, 1, 0],
[1, 1, 0, 1, 1, 1, 1]])

Method 1 - Numpy
You could achieve this using the following slight modification to your current code - alter the return value of rule30 to return np.array(next_row). Then you can use the following function:
def apply_rule(n):
rv = initial_state(n)
while rv[-1][0] == 0:
rv = np.append(rv, rule30(rv[-1].reshape(1,-1)), axis=0)
return rv
Usage:
>>> apply_rule(7)
array([[0, 0, 0, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 1, 1, 0, 0, 1, 0],
[1, 1, 0, 1, 1, 1, 1]])
Or plotted:
>>> plt.imshow(apply_rule(7), cmap='hot')
Method 2 - Lists
Alternatively, you could use the following solution without using numpy, which uses a few functions to apply the Rule 30 logic across each triple in each padded list, until the stop-condition is met.
Code:
def rule(t):
return t[0] ^ (t[1] or t[2])
def initial_state(width):
initial = [0]*width
if width%2:
initial[width // 2] = 1
else:
initial.insert(width//2, 1)
return initial
def get_triples(l):
return zip(l,l[1:],l[2:])
def rule30(l):
return [rule(t) for t in get_triples([0] + l + [0])]
def apply_rule(width):
rv = [initial_state(width)]
while not rv[-1][0]:
rv.append(rule30(rv[-1]))
return rv
Usage:
>>> apply_rule(7)
[[0, 0, 0, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 1, 0],
[1, 1, 1, 0, 0, 1, 1]]
>>> [''.join(str(y) for y in x) for x in apply_rule(7)]
['0001000',
'0011100',
'0111010',
'1110011']
Matplotlib visualisation (using either method):
import matplotlib.pyplot as plt
plt.figure(figsize=(10,6))
plt.imshow(apply_rule(250), cmap='hot')

Related

How to convert list of list binary into list of decimal?

LIST OF LIST BIN DIVIDED INTO 8 : [[0, 1, 1, 0, 0, 1, 0, 1], [0, 1, 1, 1, 0, 1, 1, 1]]
the output I want is:
[101, 119]
This is more complex but significantly faster than any kind of string manipulation as it's essentially just integer arithmetic.
from timeit import timeit
lob = [[0, 1, 1, 0, 0, 1, 0, 1], [0, 1, 1, 1, 0, 1, 1, 1]]
def v1():
result = []
for e in lob:
r = 0
for _e in e:
r = r * 2 + _e
result.append(r)
return result
def v2():
return [int(''.join([str(y) for y in x]), 2) for x in lob]
assert v1() == v2()
for func in v1, v2:
print(func.__name__, timeit(func))
Output:
v1 0.6906622060014342
v2 2.173182999999881

How to count contour (inside value, outside 0) pixels on a given 2d array?

I have the following task to solve.
I have an image (numpy array) where everything that is not the main object is 0 and the main object has some pixel counts all around (let's set all of them to 1).
What I need is to get the number of all the pixels on the contour (red squares with 1 as the value) of this object. The objects can have different forms.
Is there any way to achieve it?
OBS: The goal is to have a method that would be able to adapt to the shape of the figure, because it would be run on multiple images simultaneously.
I propose a similar solution to #user2640045 using convolution.
We can slide a filter over the array that counts the number of neighbours (left, right, top, bottom):
import numpy as np
from scipy import signal
a = np.array(
[
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 1, 1, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
]
)
filter = np.array([[0, 1, 0],
[1, 0, 1],
[0, 1, 0]])
Now we convolve the image array with the filter and :
conv = signal.convolve2d(a, filter, mode='same')
Every element that has more than zero and less than four neighbors while being active itself is a boundary element:
bounds = a * np.logical_and(conv > 0, conv < 4)
We can apply this mask to get the boundary pixels and sum them up:
>>> a[bounds].sum()
8
Here are 2 example inputs:
This is interesting and I got an elegant solution for you.
Since we can agree that contour is defined as np.array value that is greater than 0 and have at least 1 neighbor with a value of 0 we can solve it pretty stright forward and make sure it is ready for every single image you will get for life (in an Numpy array, of course...)
import numpy as np
image_pxs = np.array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 1, 1, 1, 1, 1, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]])
def get_contour(two_d_arr):
contour_pxs = 0
# Iterate of np:
for i, row in enumerate(two_d_arr):
for j, pixel in enumerate(row):
# Check neighbors
up = two_d_arr[i-1][j] == 0 if i > 0 else True
down = two_d_arr[i+1][j] == 0 if i < len(image_pxs)-1 else True
left = two_d_arr[i][j-1] == 0 if j > 0 else True
right = two_d_arr[i][j+1] == 0 if j < len(row)-1 else True
# Count distinct neighbors (empty / not empty)
sub_contour = len(list(set([up, down, left, right])))
# If at least 1 neighbor is empty and current value > 0 it is the contour
if sub_contour > 1 and pixel > 0:
# Add the number of pixels in i, j
contour_pxs += pixel
return contour_pxs
print(get_contour(image_pxs))
The output is of course 8:
8
[Finished in 97ms]

Most efficient way of comparing each element in a 2D numpy array to its 8 neighbours

So in a binary array I'm trying to find the points where a 0 and a 1 are next to each other, and redraw the array with these crossover points indicated by modifying the 0 value. Just wondering if there's a better way of comparing each of the values in a numpy array to the 8 surrounding values than using nested for loops.
Currently I have this, which compares to 4 surrounding just for readability here
for x in range(1, rows - 1):
for y in range(1, columns - 1):
if f2[x, y] == 0:
if f2[x-1, y] == 1 or f2[x+1, y] == 1 or f2[x, y-1] == 1 or f2[x, y+1] == 1:
f2[x, y] = 2
EDIT
For example
[[1, 1, 1, 1, 1, 1, 1],
[1, 1, 0, 0, 0, 1, 1],
[1, 1, 0, 0, 0, 1, 1],
[1, 1, 0, 0, 0, 1, 1],
[1, 1, 1, 1, 1, 1, 1]]
to
[[1, 1, 1, 1, 1, 1, 1],
[1, 1, 2, 2, 2, 1, 1],
[1, 1, 2, 0, 2, 1, 1],
[1, 1, 2, 2, 2, 1, 1],
[1, 1, 1, 1, 1, 1, 1]]
This problem can be solved quickly with binary morphology functions
import numpy as np
from scipy.ndimage.morphology import binary_dilation, generate_binary_structure
# Example array
f2 = np.zeros((5,5), dtype=float)
f2[2,2] = 1.
# This line determines the connectivity (all 8 neighbors or just 4)
struct_8_neighbors = generate_binary_structure(2, 2)
# Replace cell with maximum of neighbors (True if any neighbor != 0)
has_neighbor = binary_dilation(f2 != 0, structure=struct_8_neighbors)
# Was cell zero to begin with
was_zero = f2 == 0
# Update step
f2[has_neighbor & was_zero] = 2.

Code outputs right information but the formatting is incorrect, How do you segment a string?

This is my code:
def string2bin(s):
y = []
for x in s:
q = []
q.append(str(bin(ord(x))[2:].zfill(8)))
y.append(q)
return y
It is supposed to output:
string2bin('abc')
[[0, 1, 1, 0, 0, 0, 0, 1], [0, 1, 1, 0, 0, 0, 1, 0],
[0, 1, 1, 0, 0, 0, 1, 1]]
But instead outputs:
string2bin('abc')
[['01100001'], ['01100010'], ['01100011']]
Also how do you segment a string?
Thanks for any help!
Convert the string to a list of digits.
def string2bin(s):
y = []
for x in s:
q = [int(c) for c in (str(bin(ord(x))[2:].fill(8)))]
y.append(q)
return y
Probably the easiest edit to your code would be to simply change the append from string to list. As you are adding it to a list as a string, because the way ord works you are simply adding the string as a whole, not as individual values.
def string2bin(s):
y = []
for x in s:
q = []
q.append(list(bin(ord(x))[2:].zfill(8)))
y.append(q)
return y
Try this:
def string2bin(s):
return [list(map(int, str(bin(ord(x)))[2:].zfill(8))) for x in s]
print(string2bin('abc'))
It produces (up to indentation):
[[0, 1, 1, 0, 0, 0, 0, 1],
[0, 1, 1, 0, 0, 0, 1, 0],
[0, 1, 1, 0, 0, 0, 1, 1]]
You just missed the map(int, ...) and list(...) part.
So you're taking 1 char at a time out of the string then converting it to bin, removing the 0b and then filling in the rest with 0's to keep the length at 8.
The issue is this in turn gives you a string to append, not an array.
Here's my solution, not the most Pythonic but it's easy to read:
def string2bin(s):
y = []
for x in s:
q = []
adjusted = list(str(bin(ord(x)))[2:].zfill(8))
for num in adjusted:
q.append(int(num))
y.append(q)
return y
print(string2bin('abc'))
This outputs exactly what you've requested:
[[0, 1, 1, 0, 0, 0, 0, 1], [0, 1, 1, 0, 0, 0, 1, 0], [0, 1, 1, 0, 0, 0, 1, 1]]

how to use the steady state probability to select a state in each iteration of the python code?

I have an ergodic markov chain whit three states. I calculated the steady state probability.
the state present the input of my problem .
I want to solve my problem for n iteration which in each one we select the input based on the calculated steady state probability.
In the words, this is same a having three options with specific probability. and we want to select one of them randomly in each iteration.
Do you have any suggestion ??
Best,
Aissan
Let's assume you have a vector of probabilities (instead of just 3) and also that your initial state is the first one.
import random
def markov(probs, iter):
# normalize the probabilities
total = sum(probs)
probs = map(lambda e: float(e)/total, probs)
# determine the number of states
n = len(probs)
# Set the initial state
s = 0
for i in xrange(iter):
thresh = random.random()
buildup = 0
# When the sum of our probability vector is greater than `thresh`
# we've found the next state
for j in xrange(n):
buildup += probs[j]
if buildup >= thresh:
break
# Set the new state
s = j
return s
And thus
>>> markov([1,1,1], 100)
2
>>> markov([1,1,1], 100)
1
But this only returns the last state. It's easy to fix this with a neat trick, though. Let's turn this into a generator. We literally just need one more line, yield s.
def markov(probs, iter):
# ...
for i in xrange(iter):
# Yield the current state
yield s
# ...
for j in xrange(n):
# ...
Now when we call markov we don't get an immediate response.
>>> g = markov([1,1,1], 100)
>>> g
<generator object markov at 0x10fce3280>
Instead we get a generator object which is kind of like a "frozen" loop. You can step it once with next
>>> g.next()
1
>>> g.next()
1
>>> g.next()
2
Or even unwind the whole thing with list
>>> list(markov([1,1,1], 100))
[0, 0, 1, 1, 0, 0, 0, 2, 1, 1, 2, 0, 1, 0, 0, 1, 2, 2, 2, 1, 2, 0, 1, 2, 0, 1, 2, 2, 2, 2, 1, 0, 0, 0, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 0, 0, 2, 2, 1, 0, 0, 0, 2, 0, 2, 2, 1, 0, 1, 1, 1, 2, 2, 2, 2, 0, 2, 1, 0, 0, 1, 2, 0, 0, 1, 2, 2, 0, 0, 1, 2, 1, 0, 0, 1, 0, 2, 1, 1, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0, 0]

Categories