1

I have a random walk on a 2D grid with randomly initialised positions. I am looking for a condition, where if the random walk is within some range of the initial positions of the other random walks, it will stop.

While I found this easy to implement in the simple case, I am having trouble implementing it in the case of N random walks. This is due to the fact, that the code needs to check for a range of values around every initial position, except for the one, which is around the current random walk.

P.S This is my first post on stack overflow. Please, let me know if I was being too vague or did not follow the guidelines for asking questions on here.

import numpy.random as rd               #importing libraries
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import numpy.ma as ma

lsize=100
T=1000 #n of steps
N=5 #n of walks
xvec=np.zeros(N)
yvec=np.zeros(N)
xstore=np.zeros(T+1)
ystore=np.zeros(T+1)
xmat=np.zeros((T+1,N))
ymat=np.zeros((T+1,N))

for i in range(N):    #randomly assigns initial position
    xcor=rd.randint(1,lsize)
    ycor=rd.randint(1,lsize)
    xvec[i]=xcor
    yvec[i]=ycor

for i in range(N):
    xstore[0]=xvec[i]
    ystore[0]=yvec[i]
    for j in range(T):
            A=[0,1,2,3]
            temp=rd.choice(A)
            if temp==0:
                ystore[j+1]=ystore[j]+1 #up
                xstore[j+1]=xstore[j]
            elif temp==1:
                xstore[j+1]=xstore[j]+1 #right
                ystore[j+1]=ystore[j]
            elif temp==2:
                ystore[j+1]=ystore[j]-1 #down
                xstore[j+1]=xstore[j]
            elif temp==3:
                xstore[j+1]=xstore[j]-1 #left
                ystore[j+1]=ystore[j]
            xstore[j+1]=np.mod(xstore[j+1], lsize+1)
            ystore[j+1]=np.mod(ystore[j+1], lsize+1)
    xmat[:,i]=xstore
    ymat[:,i]=ystore
plt.plot(xmat,ymat)
plt.show()
Paul Brodersen
  • 11,221
  • 21
  • 38

1 Answers1

0

It's a well-asked question apart from the fact that you could have defined "within some range of the initial positions of the other random walks" better. I will assume that you mean some distance in x or y or some distance criterion in the x,y plane. The following outlines a solution for a distance criterion in x only, but the extension to other criteria is straightforward.

Basically you want a checking criterion at the end of you inner for-loop:

distance_in_x = np.mod(np.abs(xvec - xstore[j+1]), lsize)
distance_in_x[i] = np.inf # effectively mask that position
if np.any(distance_in_x <= min_distance_in_x):
    break

This assumes that you have defined min_distance_in_x somewhere above. The basic trick is that you mask the distance to origin of the walk itself by adding infinity to it. Similarly, you could also just add min_distance_in_x to ensure that the check in the following line never picks up that origin.

Edit

For a square around the origin of starting points, the script becomes:

import numpy.random as rd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import numpy.ma as ma

lsize=100
T=1000 #n of steps
N=10 #n of walks
xvec=np.zeros(N)
yvec=np.zeros(N)
xmat=np.full((T+1,N), np.nan)
ymat=np.full((T+1,N), np.nan)

min_distance_in_x = 3
min_distance_in_y = 3

# randomly assigns initial position
for i in range(N):
    xcor=rd.randint(1,lsize)
    ycor=rd.randint(1,lsize)
    xvec[i]=xcor
    yvec[i]=ycor

# walk
for i in range(N):
    xstore=np.full(T+1, np.nan)
    ystore=np.full(T+1, np.nan)
    xstore[0]=xvec[i]
    ystore[0]=yvec[i]

    for j in range(T):
        A=[0,1,2,3]
        temp=rd.choice(A)
        if temp==0:
            ystore[j+1]=ystore[j]+1 #up
            xstore[j+1]=xstore[j]
        elif temp==1:
            xstore[j+1]=xstore[j]+1 #right
            ystore[j+1]=ystore[j]
        elif temp==2:
            ystore[j+1]=ystore[j]-1 #down
            xstore[j+1]=xstore[j]
        elif temp==3:
            xstore[j+1]=xstore[j]-1 #left
            ystore[j+1]=ystore[j]

        xstore[j+1]=np.mod(xstore[j+1], lsize+1)
        ystore[j+1]=np.mod(ystore[j+1], lsize+1)

        distance_in_x = np.abs(xvec - xstore[j+1])
        distance_in_x[i] = np.inf # effectively mask that position

        distance_in_y = np.abs(yvec - ystore[j+1])
        distance_in_y[i] = np.inf # effectively mask that position

        if np.any(np.logical_and(distance_in_x <= min_distance_in_x,
                                 distance_in_y <= min_distance_in_y)):
            print("Got too close on run #{}!".format(i))
            break

    xmat[:,i]=xstore
    ymat[:,i]=ystore

for x, y in zip(xmat.T, ymat.T):
    # break the line by inserting NaNs where the boundary is crossed (i.e. a step size > 1)
    linebreaks, = np.where((np.abs(np.diff(x)) > 1) | (np.abs(np.diff(y)) > 1))
    if linebreaks.size > 0 :
        x = np.insert(x, linebreaks+1, np.nan)
        y = np.insert(y, linebreaks+1, np.nan)

    # plot lines
    plt.plot(x, y)

# plot start points
plt.gca().set_prop_cycle(None) # resets color cycle
for x, y in zip(xmat[0,:], ymat[0,:]):
    plt.plot(x, y, 'o', ms=10)

# plot end points
plt.gca().set_prop_cycle(None) # resets color cycle
for x, y in zip(xmat.T, ymat.T):
    # select last non-nan entry
    x = x[~np.isnan(x)][-1]
    y = y[~np.isnan(y)][-1]
    plt.plot(x, y, '^', ms=10)

plt.show()

enter image description here

Paul Brodersen
  • 11,221
  • 21
  • 38
  • Hi, Thanks for the answer! This is exactly what I was going for but as a distance in the xy plane (or say a square around each starting point). Edit: I have noticed that you have edited your code! – Peter Petrov Oct 08 '18 at 16:23
  • Yeah, I think that may be related to the fact that I forgot to wipe xstore and ystore on a prematurely failed attempt. Will update the code momentarily. – Paul Brodersen Oct 08 '18 at 16:27
  • This version should now produce a more interpretable image. It may be that you don't have enough reputation to post an image, otherwise it's the small image icon above the text box. – Paul Brodersen Oct 08 '18 at 16:32
  • Actually the periodic boundary conditions are not being handled correctly. Can't fix it now, as I will be AFK in a minute or two. Let me know if they give you problems. Basically, you want to also check the distance (for x) `xvec + 100 - xstore[j+1]`, compare that to `xvec - xstore[j+1]`, take the minimum of the two, and then perform the check. – Paul Brodersen Oct 08 '18 at 16:51
  • Thanks for the amazing answer. So, as I understand your script stops around the square with length 3 around all the starting points but the first one? Also do you have any recommendation for the plots around the boundary? I was thinking of masking all the values at boundary (x,y = lsize), but this would leave a gap in the plot – Peter Petrov Oct 08 '18 at 16:58
  • At the moment I cannot see any problems with the boundary condition. Also what is the reasoning behind using np.nan istead of np.zeroes? Thanks again for being so helpful! – Peter Petrov Oct 08 '18 at 17:08
  • Regarding the boundary conditions, assume that (0,0) is an origin. As it stands, a walk passing through (100, 100) is perfectly allowed, even though you seem to want to enforce periodic boundary conditions and hence the distance to (0,0) is just (1, 1). – Paul Brodersen Oct 09 '18 at 12:36
  • Regarding the use of `np.nan`: `0` is a valid value for your x and y coordinates. Using NaNs prevents any confusion with points passing through the origin, and also allows you to plot the lines without having to keep track of the length of lines explicitly (as NaNs are suppressed my matplotlib by default). – Paul Brodersen Oct 09 '18 at 12:40
  • I see, I will try to implement that today. Thank you for the thorough answer! – Peter Petrov Oct 09 '18 at 13:11
  • Made another update that removes the boundary jumps from the plots. In the future, try to keep the scope of a question as small as possible. Follow-up questions pertaining exactly to the problem at hand are fine but for only tangentially related problems you should really open another thread. Have fun! – Paul Brodersen Oct 09 '18 at 13:11