【问题标题】:Julia / Cellular Automata: efficient way to get neighborhoodJulia / 元胞自动机:获取邻域的有效方式
【发布时间】:2020-01-26 14:16:04
【问题描述】:

我想在 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

现在,第一列包含第一个单元的状态及其邻居等。

我认为这可以很容易地推广到二维和更多维度。

第一个问题:您认为这是个好主意,还是走错了路?

编辑:第一个问题的答案是否定的,第二个问题和代码示例被丢弃。

第二个问题:如果方法基本没问题,请看下图:

编辑:其他方法,这里是一维 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

下一步应该是把它扩展到二维,现在就试试……

【问题讨论】:

    标签: arrays julia cellular-automata


    【解决方案1】:

    通常,如果 CA 的单元格仅依赖于它们旁边的单元格,则通过将最后一个元素添加到前面并将第一个元素添加到后面来“包装”向量会更简单,进行模拟,然后通过再次取出第一个和最后一个元素来“解包”以获得与起始数组长度相同的结果长度。对于一维情况:

    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
    

    只要在任何给定单元格的模拟中只使用相邻单元格,这是正确的。

    如果您将其扩展到二维矩阵,您还将“包装”第一行和最后一行以及第一列和最后一列,依此类推。

    【讨论】:

    • 根据我的经验,通常使用这种方法,例如当您在核外分配计算时(因此您必须在单独的进程中将您的数组切割成许多子数组)。但是,每次更新边缘单元后,您都必须同步边缘中的值。
    【解决方案2】:

    我通常这样做的方式是使用 mod1 函数进行包装索引。

    在这种方法中,无论您的数组a 的维数是多少,当您想从位置x 移动增量dx 时,如果x 是第一个维度,则编写mod1(x+dx, size(a, 1)) 就足够了一个数组。

    这是一个简单的例子,在 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
    

    【讨论】:

    • 谢谢!我刚刚使用 mod1 实现了一个一维 CA 来获取索引,它比我的方法快三倍……不过,我在理解如何从一维到二维时遇到了一些困难。将澄清我的问题并回来查看。
    • 在我的示例中,我有一个二维矩阵。
    • 是的,我明白了,但是我尝试在一个维度上构建一个示例,在该示例中,我立即从 all 单元格中获取 all 邻居索引,例如这:[mod1.(x .+ [-1,0,1], size(gen)) for x in 1:length(gen)] ...这很好用,但不能将其转移到二维...准备问题。
    • 如果您在单独的问题中询问更多原子问题会更容易,因为如果多次更新问题很难回答。
    • 没问题,我只是在暗示什么更容易回答:)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-25
    • 1970-01-01
    相关资源
    最近更新 更多