用python模拟和绘制SAW路径(自回避随机行走问题)

自回避随机行走问题(SAW),即在格点上不与历史轨迹相撞的随机行走,本文简要说明一种利用python模拟和绘制SAW路径的方法。(完整代码在文章末尾,文字部分是按思考的顺序,代码是最终版本,中间部分代码会出现和文字有出入的内容,建议先看思路)

该路径生成碰到的主要问题是锁死,原因在于路径闭合后随机走向了空间有限的一侧,导致在达到设定步数前出现无路可走的情况。该方法的核心就是在路径闭合时选择闭合圈的外侧,避免进入圈内锁死。

为简单起见所用的是标准的方形网格,可能的前进方向只有4种,排除上一步的落点对应方向,除第一步外(第一步四向随机)可能前进的方向至多三种。设定一个每步更新的可行方向集合,接下来的限制条件均是从中去除某些项。

(Tips:方向可设为ev=[(1,0),(0,1),(-1,0),(0,-1)],行进用坐标相加,某方向的取反%4,向右(-1)%4,向左(+1)%4,后面会用上)

ev=[(1,0),(0,1),(-1,0),(0,-1)]

为了避免判断历史轨迹需要遍历,将网格赋值为零矩阵,每一步的落点赋值为1,仅需判断周围格点的值,不为0可把对应方向排除。边界设为-1,避免和路径混淆。

order=np.zeros((2*s+3,2*s+3),dtype=int)       #顺序矩阵
direc=[]                            #方向矩阵
for i in np.arange(2*s+3):          #设定边界为-1
    order[0][i]=-1
    order[-1][i]=-1
    order[i][0]=-1
    order[i][-1]=-1       
nucx=[s+1]                           #路径位置记录
nucy=[s+1]
new=(s+1,s+1)                        #暂时位置
order[s+1][s+1]=1                    #设定起点

经简单推导可知,仅路径前端的前、左、右,斜前方5格内出现1才会出现锁死的可能,故就此进行分类讨论。(示意图如下,红色为路径,黄色为讨论区块)

垂直方向(五角星处)优先判断,出现一个1记一个“wall”(每步清零),且在可行方向集合中除去该方向。

wall=0
avi=[0,1,2,3]
ju=0
if k!=0:                         #第一步完全随机
    avi.remove((direc[-1]+2)%4)
    for i in np.arange(3):
        ju=order[new[0]+ev[(direc[-1]-1+i)%4][0]][new[1]+ev[(direc[-1]-1+i)%4][1]]
        if ju!=0:
            wall+=1
            avi.remove((direc[-1]-1+i)%4)

wall==3即为锁死,可输出锁死时的步数和位置,输出图进行比对,检查出现锁死的原因。(在前期用于修正和添加限制条件,认图找端点困难时步数也一般很大,出现锁死的几率不大,算法接近完整后可换成归零条件,即重新计算至能跑到规定步数为止)

 if wall==3:
     #print(k)
     #print(new)
     restart=True
     break

wall==2即只有一个方向可走,下一步方向唯一,无需再做排除。

wall==1即有一个方向被堵,此时分为两种情况:前方被堵和侧面被堵。其中前方被堵必定出现了路径闭合,只能选择一个方向。经推导易知在闭合圈不包围原点时,应排除与"wall"所在格前一步或后一步相同的方向(在可行方向内);包围原点时,应排除相反的方向。

 

此时发现原本的数值矩阵不能用于该判断,再引入一个方向集合和顺序矩阵,顺序矩阵可以代替数值矩阵。方向集合记录每一步 的方向(ev集合的序号),顺序矩阵初始也是零矩阵,每步的落点赋值该步的顺序。判断某点前后的方向只需以该点顺序作为索引调取方向集合的值。

判断闭合圈是否包围原点则从发生闭合的端点开始(此处是从"wall"所在格开始),沿路径检查其经过象限。新增一个判断集合,若新元素和集合内已有的倒数第二个元素相同(如出现1 4 1),则该元素和倒数第一个元素都可弃置(对是否围住原点没有影响)。因需和倒数第二个数做比较,判断集合初始为[0,0]。若闭合圈包围原点,则需经过四个象限,即判断集合长度为6。函数如下:

def roll(circle,k,nucx,nucy):         #判断闭合是否包围原点
    judge=[0,0]
    for i in range(circle,k-1):
        flag=0
        if (nucx[i-1]==s+1 and nucx[i]>s+1 and nucy[i]>s+1) or (nucy[i-1]==s+1 and nucy[i]>s+1 and nucx[i]>s+1):
            judge.append(1)
            flag=1
        elif (nucx[i-1]==s+1 and nucx[i]<s+1 and nucy[i]>s+1) or (nucy[i-1]==s+1 and nucy[i]>s+1 and nucx[i]<s+1):
            judge.append(2)
            flag=1
        elif (nucx[i-1]==s+1 and nucx[i]<s+1 and nucy[i]<s+1) or (nucy[i-1]==s+1 and nucy[i]<s+1 and nucx[i]<s+1):
            judge.append(3)
            flag=1
        elif (nucx[i-1]==s+1 and nucx[i]>s+1 and nucy[i]<s+1) or (nucy[i-1]==s+1 and nucy[i]<s+1 and nucx[i]>s+1):
            judge.append(4)
            flag=1
        if flag==1:
            if judge[-1]==judge[-3]:
                judge.pop(-1)
                judge.pop(-2)
            elif judge[-1]==judge[-2]:
                judge.pop(-1)
    if len(judge)==6:
        return 0
    else:
        return 1

当wall==1是侧面被堵时,则需进一步判断两个可行方向所夹的斜前方格是否为>0。若不>0则两侧互通,都可行;若>0则下一步必将发生路径闭合。判断方法类似,但闭合起点变为该斜方格,当闭合圈不包围原点时排除闭合起点后一步的方向,包围时排除闭合起点前一步的反方向。

if wall==1:
    if order[new[0]+ev[direc[-1]][0]][new[1]+ev[direc[-1]][1]]>1:        #前方被堵
        circle=int(order[new[0]+ev[direc[-1]][0]][new[1]+ev[direc[-1]][1]])
        may1=direc[circle-2]
        may2=direc[circle-1]
        if roll(circle,k,nucx,nucy):
            if avi[0]==may1 or avi[1]==may1:
                may3=may1
            else:
                may3=may2
            avi.remove(may3)
        else:
            if avi[0]==may1 or avi[1]==may1:
                may3=(may1+2)%4
            else:
                may3=(may2+2)%4
            avi.remove(may3)
    elif order[new[0]+ev[(direc[-1]+1)%4][0]][new[1]+ev[(direc[-1]+1)%4][1]]>1:  #左侧被堵
        circle=int(order[new[0]+ev[(direc[-1]-1)%4][0]+ev[direc[-1]][0]][new[1]+ev[(direc[-1]-1)%4][1]+ev[direc[-1]][1]])
        if circle>1:
            if roll(circle,k,nucx,nucy):
                avi.remove(direc[circle-1])
            else:
                avi.remove((direc[circle-2]+2)%4)
    elif order[new[0]+ev[(direc[-1]-1)%4][0]][new[1]+ev[(direc[-1]-1)%4][1]]>1:   #右侧被堵
        circle=int(order[new[0]+ev[(direc[-1]+1)%4][0]+ev[direc[-1]][0]][new[1]+ev[(direc[-1]+1)%4][1]+ev[direc[-1]][1]])
        if circle>1:
            if roll(circle,k,nucx,nucy):
                avi.remove(direc[circle-1])
            else:
                avi.remove((direc[circle-2]+2)%4)

wall==0时,进行类似wall==1侧面被堵时的判断。需进一步判断两个可行方向所夹的斜前方格是否>0,若>0则判断闭合圈是否包围原点,不包围则排除闭合起点的前后两步方向,包围则排除相反方向。

elif wall==0:
    if order[new[0]+ev[(direc[-1]+1)%4][0]+ev[direc[-1]][0]][new[1]+ev[(direc[-1]+1)%4][1]+ev[direc[-1]][1]]>1:
        circle=int(order[new[0]+ev[(direc[-1]+1)%4][0]+ev[direc[-1]][0]][new[1]+ev[(direc[-1]+1)%4][1]+ev[direc[-1]][1]])
        if roll(circle,k,nucx,nucy):
            if direc[circle-1] in avi:
                avi.remove(direc[circle-1])
            if direc[circle-2] in avi:
                avi.remove(direc[circle-2])
        else:
            if (direc[circle-1]+2)%4 in avi:
                avi.remove((direc[circle-1]+2)%4)
            if (direc[circle-2]+2)%4 in avi:
                avi.remove((direc[circle-2]+2)%4)
    if order[new[0]+ev[(direc[-1]-1)%4][0]+ev[direc[-1]][0]][new[1]+ev[(direc[-1]-1)%4][1]+ev[direc[-1]][1]]>1:
        circle=int(order[new[0]+ev[(direc[-1]-1)%4][0]+ev[direc[-1]][0]][new[1]+ev[(direc[-1]-1)%4][1]+ev[direc[-1]][1]])
        if roll(circle,k,nucx,nucy):
            if direc[circle-1] in avi:
                avi.remove(direc[circle-1])
            if direc[circle-2] in avi:
                avi.remove(direc[circle-2])
        else:
            if (direc[circle-1]+2)%4 in avi:
                avi.remove((direc[circle-1]+2)%4)
            if (direc[circle-2]+2)%4 in avi:
                avi.remove((direc[circle-2]+2)%4)

最后在可行方向集合avi中随机抽取方向决定下一步方向,保证随机性。更新暂存位置集合new、路径位置集合nuc、方向集合direc,落点处顺序矩阵order的赋值。

ra=random.choice(avi)                          #抽取方向
new=(new[0]+ev[ra][0],new[1]+ev[ra][1])        #更新暂时位置
nucx.append(new[0])                            #更新路径位置
nucy.append(new[1])
order[new[0]][new[1]]=k+2                      #更新顺序矩阵
direc.append(ra)                               #更新方向集合

用matplotlib绘制路径(nuc)。

plt.figure(figsize=(60,60))
plt.plot(nucx,nucy)
plt.grid(True)

 十万步效果图:

缺陷

其实不难发现原点周边和边界会有特殊的锁死情况,不能用以上方法排除。边界问题可以用扩大边界的方法回避,原点目前还没有想到解决办法。但碰到因原点锁死的概率并不大,且碰上时步数也一般较小,就先用重算的方式摆烂了(懒,改天有心情有空就来完善)。

目前参数下输出十万步不会超过5秒,边界扩大2倍后输出五十万步在20秒左右。大步数下边界(和内存)是主要限制,numpy.zeros默认浮点型,dtype为int型后可以进一步提高步数上限(主要是顺序矩阵)。若能解决边界锁死的问题可以提高空间利用率,大辐度提高上限,目前的想法是利用回退,但还没写,改天吧(但愿)



附完整代码(后期优化了一下但没完全优化,肯定还有不少简化空间,凑合看吧):

import matplotlib.pyplot as plt # 导入图形库
import numpy as np
import random
%matplotlib inline
def roll(circle,k,nucx,nucy):         #判断闭合是否包围原点
    judge=[0,0]
    for i in range(circle,k-1):
        flag=0
        if (nucx[i-1]==s+1 and nucx[i]>s+1 and nucy[i]>s+1) or (nucy[i-1]==s+1 and nucy[i]>s+1 and nucx[i]>s+1):
            judge.append(1)
            flag=1
        elif (nucx[i-1]==s+1 and nucx[i]<s+1 and nucy[i]>s+1) or (nucy[i-1]==s+1 and nucy[i]>s+1 and nucx[i]<s+1):
            judge.append(2)
            flag=1
        elif (nucx[i-1]==s+1 and nucx[i]<s+1 and nucy[i]<s+1) or (nucy[i-1]==s+1 and nucy[i]<s+1 and nucx[i]<s+1):
            judge.append(3)
            flag=1
        elif (nucx[i-1]==s+1 and nucx[i]>s+1 and nucy[i]<s+1) or (nucy[i-1]==s+1 and nucy[i]<s+1 and nucx[i]>s+1):
            judge.append(4)
            flag=1
        if flag==1:
            if judge[-1]==judge[-3]:
                judge.pop(-1)
                judge.pop(-2)
            elif judge[-1]==judge[-2]:
                judge.pop(-1)
    if len(judge)==6:
        return 0
    else:
        return 1
    
    
    
s=5000           #边界
target=100000    #目标步数
restart=True     #是否重新循环
while restart:
    restart=False
    order=np.zeros((2*s+3,2*s+3),dtype=int)       #顺序矩阵
    direc=[]                            #方向矩阵
    for i in np.arange(2*s+3):          #设定边界为-1
        order[0][i]=-1
        order[-1][i]=-1
        order[i][0]=-1
        order[i][-1]=-1
    ev=[(1,0),(0,1),(-1,0),(0,-1)]       
    nucx=[s+1]                           #路径位置记录
    nucy=[s+1]
    new=(s+1,s+1)                        #暂时位置
    order[s+1][s+1]=1                    #设定起点
    for k in np.arange(target):
        wall=0                         
        avi=[0,1,2,3]                    #可行方向集合
        ju=0                             #判断wall
        if k!=0:                         #第一步完全随机
            avi.remove((direc[-1]+2)%4)
            for i in np.arange(3):
                ju=order[new[0]+ev[(direc[-1]-1+i)%4][0]][new[1]+ev[(direc[-1]-1+i)%4][1]]
                if ju!=0:
                    wall+=1
                    avi.remove((direc[-1]-1+i)%4)

            if wall==3:
                #print(k)
                #print(new)
                restart=True            #重新循环
                break
            if wall==1:
                if order[new[0]+ev[direc[-1]][0]][new[1]+ev[direc[-1]][1]]>1:        #前方被堵
                    circle=int(order[new[0]+ev[direc[-1]][0]][new[1]+ev[direc[-1]][1]])
                    may1=direc[circle-2]
                    may2=direc[circle-1]
                    if roll(circle,k,nucx,nucy):
                        if avi[0]==may1 or avi[1]==may1:
                            may3=may1
                        else:
                            may3=may2
                        avi.remove(may3)
                    else:
                        if avi[0]==may1 or avi[1]==may1:
                            may3=(may1+2)%4
                        else:
                            may3=(may2+2)%4
                        avi.remove(may3)
                elif order[new[0]+ev[(direc[-1]+1)%4][0]][new[1]+ev[(direc[-1]+1)%4][1]]>1:  #左侧被堵
                    circle=int(order[new[0]+ev[(direc[-1]-1)%4][0]+ev[direc[-1]][0]][new[1]+ev[(direc[-1]-1)%4][1]+ev[direc[-1]][1]])
                    if circle>1:
                        if roll(circle,k,nucx,nucy):
                            avi.remove(direc[circle-1])
                        else:
                            avi.remove((direc[circle-2]+2)%4)
                elif order[new[0]+ev[(direc[-1]-1)%4][0]][new[1]+ev[(direc[-1]-1)%4][1]]>1:   #右侧被堵
                    circle=int(order[new[0]+ev[(direc[-1]+1)%4][0]+ev[direc[-1]][0]][new[1]+ev[(direc[-1]+1)%4][1]+ev[direc[-1]][1]])
                    if circle>1:
                        if roll(circle,k,nucx,nucy):
                            avi.remove(direc[circle-1])
                        else:
                            avi.remove((direc[circle-2]+2)%4)
            elif wall==0:
                if order[new[0]+ev[(direc[-1]+1)%4][0]+ev[direc[-1]][0]][new[1]+ev[(direc[-1]+1)%4][1]+ev[direc[-1]][1]]>1:
                    circle=int(order[new[0]+ev[(direc[-1]+1)%4][0]+ev[direc[-1]][0]][new[1]+ev[(direc[-1]+1)%4][1]+ev[direc[-1]][1]])
                    if roll(circle,k,nucx,nucy):
                        if direc[circle-1] in avi:
                            avi.remove(direc[circle-1])
                        if direc[circle-2] in avi:
                            avi.remove(direc[circle-2])
                    else:
                        if (direc[circle-1]+2)%4 in avi:
                            avi.remove((direc[circle-1]+2)%4)
                        if (direc[circle-2]+2)%4 in avi:
                            avi.remove((direc[circle-2]+2)%4)
                if order[new[0]+ev[(direc[-1]-1)%4][0]+ev[direc[-1]][0]][new[1]+ev[(direc[-1]-1)%4][1]+ev[direc[-1]][1]]>1:
                    circle=int(order[new[0]+ev[(direc[-1]-1)%4][0]+ev[direc[-1]][0]][new[1]+ev[(direc[-1]-1)%4][1]+ev[direc[-1]][1]])
                    if roll(circle,k,nucx,nucy):
                        if direc[circle-1] in avi:
                            avi.remove(direc[circle-1])
                        if direc[circle-2] in avi:
                            avi.remove(direc[circle-2])
                    else:
                        if (direc[circle-1]+2)%4 in avi:
                            avi.remove((direc[circle-1]+2)%4)
                        if (direc[circle-2]+2)%4 in avi:
                            avi.remove((direc[circle-2]+2)%4)

        ra=random.choice(avi)                          #抽取方向
        new=(new[0]+ev[ra][0],new[1]+ev[ra][1])        #更新暂时位置
        nucx.append(new[0])                            #更新路径位置
        nucy.append(new[1])
        order[new[0]][new[1]]=k+2                      #更新顺序矩阵
        direc.append(ra)                               #更新方向集合

plt.figure(figsize=(60,60))
plt.plot(nucx,nucy)
plt.grid(True)

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值