Julia / Cellular Automata:获得邻居的有效方式
Julia / Cellular Automata: efficient way to get neighborhood
我想在 Julia 中实现元胞自动机 (CA)。尺寸应该换行,这意味着:最左边的单元格的左邻居是最右边的单元格等。
一个关键问题是:如何让一个细胞的邻居计算它在下一代的状态?由于维度应该被包装并且 Julia 不允许负索引(如 Python)我有这个想法:
算一维CA,一代是一维数组:
0 0 1 0 0
如果我们创建一个二维数组,第一行右移,第三行左移,如下所示:
0 0 0 1 0
0 0 1 0 0
0 1 0 0 0
现在,第一列包含第一个单元格及其邻居等的状态。
我认为这可以很容易地推广到二维和更多维度。
第一个问题:你觉得这是个好主意,还是走错路了?
编辑:第一个问题的答案是否定的,第二个问题和代码示例被丢弃。
第二个问题:如果方法基本没问题,请看下图:
编辑:其他方法,这里是 1D CA 的精简版本,使用 mod1() 获取邻域索引,正如 Bogumił Kamiński 所建议的。
对于任何单元格:
- 所有索引的数组
- 所有邻域状态的 B 数组
- C状态转换为一个整数
-D 查找下一个状态
function digits2int(digits, base=10)
int = 0
for digit in digits
int = int * base + digit
end
return int
end
gen = [0,0,0,0,0,1,0,0,0,0,0]
rule = [0,1,1,1,1,0,0,0]
function nextgen(gen, rule)
values = [mod1.(x .+ [-1,0,1], size(gen)) for x in 1:length(gen)] # A
values = [gen[value] for value in values] # B
values = [digits2int(value, 2) for value in values] # C
values = [rule[value+1] for value in values] # D
return values
end
for _ in 1:100
global gen
println(gen)
gen = nextgen(gen, rule)
end
下一步应该是将其扩展到二维,现在就试试...
通常,如果 CA 的单元格仅依赖于它们旁边的单元格,则只需 "wrap" 向量通过将最后一个元素添加到前面并将第一个元素添加到后面会更简单,进行模拟,然后 "unwrap" 再次取走第一个和最后一个元素以获得与起始数组长度相同的结果长度。对于一维情况:
const lines = 10
const start = ".........#........."
const rules = [90, 30, 14]
rule2poss(rule) = [rule & (1 << (i - 1)) != 0 for i in 1:8]
cells2bools(cells) = [cells[i] == '#' for i in 1:length(cells)]
bools2cells(bset) = prod([bset[i] ? "#" : "." for i in 1:length(bset)])
function transform(bset, ruleposs)
newbset = map(x->ruleposs[x],
[bset[i + 1] * 4 + bset[i] * 2 + bset[i - 1] + 1
for i in 2:length(bset)-1])
vcat(newbset[end], newbset, newbset[1])
end
const startset = cells2bools(start)
for rul in rules
println("\nUsing Rule $rul:")
bset = vcat(startset[end], startset, startset[1]) # wrap ends
rp = rule2poss(rul)
for _ in 1:lines
println(bools2cells(bset[2:end-1])) # unwrap ends
bset = transform(bset, rp)
end
end
只要在任何给定单元格的模拟中只使用相邻单元格,这是正确的。
如果将其扩展到二维矩阵,您还会 "wrap" 第一行和最后一行以及第一列和最后一列,依此类推。
我通常采用的方式是使用 mod1
函数进行包装索引。
在这种方法中,无论你的数组 a
是什么维度,当你想从位置 x
移动增量 dx
时,写 mod1(x+dx, size(a, 1))
如果 x
是数组的第一个维度。
这是一个简单的例子,在 2D 环面上随机游走,计算给定单元格被访问的次数(这里我另外使用广播来处理一个表达式中的所有维度):
function randomwalk()
a = zeros(Int, 8, 8)
pos = (1,1)
for _ in 1:10^6
# Von Neumann neighborhood
dpos = rand(((1,0), (-1,0), (0,1), (0,-1)))
pos = mod1.(pos .+ dpos, size(a))
a[pos...] += 1
end
a
end
我想在 Julia 中实现元胞自动机 (CA)。尺寸应该换行,这意味着:最左边的单元格的左邻居是最右边的单元格等。
一个关键问题是:如何让一个细胞的邻居计算它在下一代的状态?由于维度应该被包装并且 Julia 不允许负索引(如 Python)我有这个想法:
算一维CA,一代是一维数组:
0 0 1 0 0
如果我们创建一个二维数组,第一行右移,第三行左移,如下所示:
0 0 0 1 0
0 0 1 0 0
0 1 0 0 0
现在,第一列包含第一个单元格及其邻居等的状态。
我认为这可以很容易地推广到二维和更多维度。
第一个问题:你觉得这是个好主意,还是走错路了?
编辑:第一个问题的答案是否定的,第二个问题和代码示例被丢弃。
第二个问题:如果方法基本没问题,请看下图:
编辑:其他方法,这里是 1D CA 的精简版本,使用 mod1() 获取邻域索引,正如 Bogumił Kamiński 所建议的。
对于任何单元格: - 所有索引的数组 - 所有邻域状态的 B 数组 - C状态转换为一个整数 -D 查找下一个状态
function digits2int(digits, base=10)
int = 0
for digit in digits
int = int * base + digit
end
return int
end
gen = [0,0,0,0,0,1,0,0,0,0,0]
rule = [0,1,1,1,1,0,0,0]
function nextgen(gen, rule)
values = [mod1.(x .+ [-1,0,1], size(gen)) for x in 1:length(gen)] # A
values = [gen[value] for value in values] # B
values = [digits2int(value, 2) for value in values] # C
values = [rule[value+1] for value in values] # D
return values
end
for _ in 1:100
global gen
println(gen)
gen = nextgen(gen, rule)
end
下一步应该是将其扩展到二维,现在就试试...
通常,如果 CA 的单元格仅依赖于它们旁边的单元格,则只需 "wrap" 向量通过将最后一个元素添加到前面并将第一个元素添加到后面会更简单,进行模拟,然后 "unwrap" 再次取走第一个和最后一个元素以获得与起始数组长度相同的结果长度。对于一维情况:
const lines = 10
const start = ".........#........."
const rules = [90, 30, 14]
rule2poss(rule) = [rule & (1 << (i - 1)) != 0 for i in 1:8]
cells2bools(cells) = [cells[i] == '#' for i in 1:length(cells)]
bools2cells(bset) = prod([bset[i] ? "#" : "." for i in 1:length(bset)])
function transform(bset, ruleposs)
newbset = map(x->ruleposs[x],
[bset[i + 1] * 4 + bset[i] * 2 + bset[i - 1] + 1
for i in 2:length(bset)-1])
vcat(newbset[end], newbset, newbset[1])
end
const startset = cells2bools(start)
for rul in rules
println("\nUsing Rule $rul:")
bset = vcat(startset[end], startset, startset[1]) # wrap ends
rp = rule2poss(rul)
for _ in 1:lines
println(bools2cells(bset[2:end-1])) # unwrap ends
bset = transform(bset, rp)
end
end
只要在任何给定单元格的模拟中只使用相邻单元格,这是正确的。
如果将其扩展到二维矩阵,您还会 "wrap" 第一行和最后一行以及第一列和最后一列,依此类推。
我通常采用的方式是使用 mod1
函数进行包装索引。
在这种方法中,无论你的数组 a
是什么维度,当你想从位置 x
移动增量 dx
时,写 mod1(x+dx, size(a, 1))
如果 x
是数组的第一个维度。
这是一个简单的例子,在 2D 环面上随机游走,计算给定单元格被访问的次数(这里我另外使用广播来处理一个表达式中的所有维度):
function randomwalk()
a = zeros(Int, 8, 8)
pos = (1,1)
for _ in 1:10^6
# Von Neumann neighborhood
dpos = rand(((1,0), (-1,0), (0,1), (0,-1)))
pos = mod1.(pos .+ dpos, size(a))
a[pos...] += 1
end
a
end