Search code examples
rrandom-walk

Subscript out of bounds, iteration for a bound random walk


I am trying to write a function that generates a random walk bound by the unit sphere, resulting in the last location of the random walk given by a vector. In trying to figure out where my function is breaking, I have tried piecing it out like this:

norm_vec <- function(x) sqrt(sum(x^2))
sphere_start_checker <- function() {
        repeat {
        start_loc <- runif(3, min = -1, max = 1)
        if (norm_vec(start_loc) < 1) return(start_loc)
        }
}

n <- 10
set.seed(1)
new_loc <- as.matrix(t(sphere_start_checker()))
temp_loc <- as.matrix(t(c(0, 0, 0)))
for(i in n) {
repeat {
        temp_loc <- new_loc[i, ] + runif(3, min = -.01, max = .01)
        if (norm_vec(temp_loc) < 1) {
                return(new_loc <- rbind(new_loc, temp_loc))
        }
}
}
new_loc

I get the error: Error in new_loc[i, ] : subscript out of bounds

However, when I manually iterate the code outside of the for loop, everything works fine. I assume this has something to do with R's scoping rules, and I have tried messing with assigning the result of the rbind to the global environment, but that does not work.

Initially, I tried writing the function like this:

randomwalk_sphere <- function(n, radius, stepmax) {
        # initialize sphere bounds
        sphere_radius <- as.double(radius)
        # initialize random starting vector
        # while loop that adds a random vector to our start_loc n times, repeating once boundary condition is met
        loop <- 0
        new_loc <- sphere_start_checker()
        while(loop <= n) {
                repeat {
                        temp_loc <- new_loc + runif(3, min = -stepmax, max = stepmax)
                        if (norm_vec(temp_loc) < sphere_radius) {
                                return(assign("new_loc", temp_loc, env = .GlobalEnv))
                        } 
                }
                loop <- loop + 1
        }
        new_loc
}

but when I do it this way, the while loop does not seem to work and I just get the initial new_loc coordinate from the uniform distribution rather than from a random walk. I realize that using rbind is probably a more correct way of doing this, but I am interested in learning why this approach does not work.


Solution

  • Some thoughts:

    • while you "fixed" the issue with for (i in n), the canonical (more-common) method is typically to keep n as a single-length count, and use seq_len(n) or similar within the for call, such as n <- 10; for (i in seq_len(n)).

    • return in anything other than a formal function(.){..} declaration is not right; if you want to stop a repeat, use break. As demonstrated here, the scope of the re-assignment is not right, so the new_loc is never really reassigned with all of the iterations' results.

    Ultimately, once you stop trying to return from a non-function, it works.

    n <- 10
    set.seed(1)
    new_loc <- as.matrix(t(sphere_start_checker()))
    temp_loc <- as.matrix(t(c(0, 0, 0)))
    for (i in seq_len(n)) {
      repeat {
        temp_loc <- new_loc[i, ] + runif(3, min = -.01, max = .01)
        if (norm_vec(temp_loc) < 1) {
          new_loc <- rbind(new_loc, temp_loc)
          break
        }
      }
    }
    new_loc
    #                [,1]       [,2]      [,3]
    #          -0.4689827 -0.2557522 0.1457067
    # temp_loc -0.4608185 -0.2617186 0.1536745
    # temp_loc -0.4519250 -0.2585026 0.1562568
    # temp_loc -0.4606893 -0.2643831 0.1497879
    # temp_loc -0.4569488 -0.2667010 0.1551848
    # temp_loc -0.4569948 -0.2623487 0.1650229
    # temp_loc -0.4593941 -0.2567998 0.1737170
    # temp_loc -0.4651513 -0.2537663 0.1662281
    # temp_loc -0.4698069 -0.2560440 0.1564959
    # temp_loc -0.4721591 -0.2486502 0.1533029
    # temp_loc -0.4725175 -0.2466589 0.1531737