R/getpath.R

##' @title Determine the new position when moving from the current position
##'        in a given direction
##' @param curpos current position (i, j) in the occupancy matrix
##' @param dir direction in which we move ("d", "u", "r" or "l")
##' @param method choice of method ("in.occupancy" means the (current/new)
##'        position is given in terms of (row, column) indices in the
##'        occupancy matrix; "in.plane" means the directions are
##'        interpreted as in the (x,y)-plane).
##' @return new position in the occupancy matrix
##' @author Marius Hofert and Wayne Oldford
move <- function(curpos, dir, method=c("in.occupancy", "in.plane"))
{
    method <- match.arg(method)
    curpos +
        if (method == "in.plane") {
            switch(dir,
                   "d" = { c( 0, -1) },
                   "u" = { c( 0,  1) },
                   "r" = { c( 1,  0) },
                   "l" = { c(-1,  0) },
                   stop("Wrong 'dir'"))

        } else {
            switch(dir,
                   "d" = { c( 1,  0) },
                   "u" = { c(-1,  0) },
                   "r" = { c( 0,  1) },
                   "l" = { c( 0, -1) },
                   stop("Wrong 'dir'"))
        }
}

##' @title Compute turns for zigzag
##' @param nPlots total number of plots
##' @param n2dcol number of columns of 2d plots (>= 1)
##' @param method character string indicating which zigzag method to use
##' @return turns
##' @author Marius Hofert and Wayne Oldford
get_turns_for_zigzag <- function(nPlots, n2dcol,
                                 method=c("tidy", "double.zigzag", "single.zigzag"))
{
    ## Main idea: Determine the pattern which repeats rowblock-wise
    ##            (after going to the right and then back to the left)
    stopifnot(nPlots >= 4, # smaller nPlots relate to special cases dealt with in get_path()
              n2dcol >= 2)
    method <- match.arg(method)
    pattern2d <- rep(c("r", "l"), each=2*(n2dcol-1)) # r,r,..., l,l,... for 2d plots
    vdirs2d1row <- if(method=="single.zigzag") rep("d", n2dcol-1) else {
        if(n2dcol <= 2) "d" else c(rep_len(c("d", "u" ), n2dcol-3), "d", "d") # up/down for 2d plots for one row (length = n2dcol - 1; so, e.g. only from right to left)
    }
    vdirs2d <- rep(vdirs2d1row, times=2) # up/down for 2d plots for one block of rows (r,r,..., l,l,...)
    pattern2d[2*seq_along(vdirs2d)] <- vdirs2d # merge the ups/downs into pattern2d
    pattern <- rep(pattern2d, each=2) # bring in 1d plots (by repeating each direction twice)
    c("d", rep_len(pattern, nPlots-1)) # attach the first 1d plot separately and repeat the repeat pattern as often as required
}

##' @title Determine the next position to move to and the turn out of there
##' @param plotNo current plot number
##' @param nPlots total number of plots
##' @param curpath the current path
##' @return a list containing the next position to move to (nextpos) and the turn
##'         out of there (nextout); Interpretation:
##'         nextpos: position of plot number plotNo+1 in the (non-trimmed) occupancy matrix
##'         nextout: turn out of nextpos
##' @author Marius Hofert and Wayne Oldford
##' @note - This assumes that the last plot is a 1d plot!
##'       - We start in (1, 2) and also have an additional last column in the occupancy
##'         matrix to have the first and last column left in case we end up there with
##'         the last 1d plot; this cannot happen for 'zigzag' but for 'tidy'.
next_move_tidy <- function(plotNo, nPlots, curpath)
{
    stopifnot(plotNo >= 1, nPlots >= 4, # smaller nPlots relate to special cases dealt with in get_path()
              is.list(curpath))
    nPlotsLeft <- nPlots - plotNo # number of plots left
    if(nPlotsLeft <= 0)
        stop("Wrong number of plots left.") # next_move_tidy() should not be called in this case

    ## Deal with special cases first
    if(plotNo==1) return(list(nextpos=c(2, 2), nextout="r")) # 1st plot (1d); next move/turn is "r"
    if(plotNo==2) return(list(nextpos=c(2, 3), nextout="r")) # 2nd plot (2d); next move/turn is "r"
    if(plotNo==3) # 3rd plot (1d); next move/turn is either up (only if there's only 1 1d plot left) or down
        return(list(nextpos=c(2, 4), nextout=if(nPlotsLeft <= 2) "u" else "d"))

    ## Now plotNo >= 4 and there are >= 1 plots left
    curpos <- curpath$positions[plotNo,] # current position
    curin  <- curpath$turns[plotNo-1] # turn into the current position
    curout <- curpath$turns[plotNo] # turn out of the current position
    nextpos <- move(curpos, curout) # next position to move to

    ## If plotNo is even (2d plot)
    ## Note: This case also applies if nPlotsLeft==1
    if(plotNo %%2 == 0)
        return(list(nextpos=nextpos, nextout=curout))

    ## Now this is the case: plotNo is >= 5, odd (1d plot) and there are >= 2 plots left

    ## Determine the current horizontal moving direction.
    ## If the current 1d plot is vertical (or: horizontal), the horizontal direction is the
    ## turn into the current (or: last) position.
    ## => We simply consider the turn into the current position and the
    ##    one before to determine the horizontal moving direction.
    rinlast2 <- "r" %in% curpath$turns[(plotNo-2):(plotNo-1)]
    linlast2 <- "l" %in% curpath$turns[(plotNo-2):(plotNo-1)]
    if((rinlast2 + linlast2) != 1) # defensive programming
        stop("Algorithm to determine horizontal moving direction is wrong. This should not happen.")
    horizdir <- if(rinlast2) "r" else "l" # current horizontal moving direction

    ## Determine the distance to the (left or right) margin
    ncolOcc <- ncol(curpath$occupancy)
    dist <- if(horizdir=="r") ncolOcc-curpos[2] else curpos[2]-1
    stopifnot(dist >= 0) # defensive programming

    ## Auxiliary function for checking the existence of a position in the occupancy matrix
    posExists <- function(pos, occupancy)
        (1 <= pos[1] && pos[1] <= nrow(occupancy)) &&
            (1 <= pos[2] && pos[2] <= ncol(occupancy))

    ## We are sitting at a 1d plot and have to determine how to leave
    ## the next 2d plot
    nextout <- if(curout %in% c("d", "u")) {

        ## The 1d plot is horizontal (curout "u" or "d")
        if(dist == 0) stop("dist == 0. This should not happen.")
        ## If we have at most 2 plots left, decide where to put the last 1d plot
        if(nPlotsLeft <= 2) {
            ## Check the location of the 2d plot which comes after the next 2d plot
            ## in opposite horizontal moving direction.
            pos2check <- c(curpos[1] + if(curout=="u") -1 else 1, curpos[2] + if(horizdir=="r") -2 else 2)
            exists <- posExists(pos2check, occupancy=curpath$occupancy)
            ## If it does not exist (can only happen if curout="d" in which case
            ## the occupancy matrix is missing a new row), then put the
            ## last 1d plot in opposite horizontal moving direction if we are near
            ## the margin (otherwise we would occupy an additional column) and put it
            ## in the horizontal moving direction otherwise (if we are 'inside' the
            ## occupancy matrix)
            if(!exists) { # we will be in a new row (curout must be "d" in this case)
                stopifnot(curout=="d") # defensive programming
                if(dist <= 2) {
                    if(horizdir == "r") "l" else "r"
                } else {
                    horizdir
                }
            } else { # exists
                ## If this position exists, then change the horizontal moving direction
                ## if and only if it is not occupied (otherwise we can't go there)
                if(curpath$occupancy[pos2check[1], pos2check[2]] == 0) { # not occupied
                    if(horizdir == "r") "l" else "r"
                } else {
                    horizdir
                }
            }
        } else { # nPlotsLeft >= 3 (=> at least two more 2d plots)
            ## Change the horizontal moving direction if and only if we are at
            ## the boundary (clear).
            if(dist <= 2) {
                if(horizdir == "r") "l" else "r"
            } else {
                horizdir
            }
        }

    } else { # curout "l" or "r"

        ## The 1d plot is vertical (curout "l" or "r")
        if(curpath$turns[plotNo-2] %in% c("l", "r")) # defensive programming; how we entered last 2d plot must be l or r
            stop("Last 2d plot was entered in the wrong direction. This should not happen.")
        if(dist <= 1) {
            stop("dist <= 1. This should not happen.") # ... as we don't call next_move_tidy() for the last 1d plot
        } else { # dist >= 2; note that dist==2 and dist==3 are possible

            ## Auxiliary function to determine how many plots fit in the next U-turn
            UturnLength <- function(curpos, horizdir, occupancy) {
                ## Check whether 1 or 2 plot(s) fit in
                pos2check <- c(curpos[1]-2, curpos[2] + if(horizdir=="r") 1 else -1)
                exists <- posExists(pos2check, occupancy=occupancy)
                if(exists && occupancy[pos2check[1], pos2check[2]] > 0) return(1)
                if(!exists) return(2) # ... we can't put in more plots
                ## Check whether 4 plots fit in
                pos2check <- pos2check + c(0, if(horizdir=="r") 2 else -2)
                exists <- posExists(pos2check, occupancy=occupancy)
                if(!exists || (exists && occupancy[pos2check[1], pos2check[2]] > 0))
                    return(4) # ... we can't put in more plots
                ## Check whether 6 plots fit in
                pos2check <- pos2check + c(2, 0)
                exists <- posExists(pos2check, occupancy=occupancy)
                if(!exists || (exists && occupancy[pos2check[1], pos2check[2]] > 0))
                    return(6) # ... we can't put in more plots
                ## Check whether 8 or >= 10 plots fit in
                pos2check <- pos2check + c(0, if(horizdir=="r") 2 else -2)
                exists <- posExists(pos2check, occupancy=occupancy)
                if(!exists || (exists && occupancy[pos2check[1], pos2check[2]] > 0))
                    return(8) else return(10) # here means "at least 10"
            }

            ## Determine the number of plots along the U-turn starting from the current position
            Ulen <- UturnLength(curpos, horizdir=horizdir, occupancy=curpath$occupancy)
            ## If in the second row or there are more plots left than can fit into a U-turn,
            ## go down, else go up; note: if Ulen >= 10, we can always take
            ## the U-turn, so we can always go up
            if(curpos[1] <= 2 || (Ulen < 10 && nPlotsLeft > Ulen)) "d" else "u"

        }

    }
    list(nextpos=nextpos, nextout=nextout) # nextout = turn out of next position
}

##' @title Computing the path according to the provided method
##' @param nVars number of variables (>= 1)
##' @param n2dcol number of columns of 2d plots (>= 1)
##' @param method character string indicating the method according to which the
##'        path is built
##' @param last1d logical indicating whether the last 1d plot should be omitted
##'        from the path
##' @return the path, a list containing the turns, the positions (indices in the
##'         occupancy matrix) and the the occupancy matrix
##' @author Marius Hofert and Wayne Oldford
get_path <- function(nVars, turns=NULL, n2dcol=5,
                     method=c("tidy", "double.zigzag", "single.zigzag"), last1d=TRUE)
{
    ## Deal with the case that turns have been given (we need to construct
    ## the positions in the occupancy matrix and the occupancy matrix itself)
    if(!is.null(turns)) {
        hrange <- c(0, 0) # horizontal range covered so far
        vrange <- c(0, 0) # vertical range covered so far
        positions <- matrix(0, nrow=length(turns), ncol=2,
                            dimnames=list(NULL, c("x", "y"))) # matrix of positions
        loc <- c(0, 0) # where we are at the moment (start)
        if(length(turns) > 1) { # if length(turns)==1, we only have one 1d plot (nothing to do as positions is already initialized with 0)
            for(i in 2:length(turns)) { # loop over all turns
                loc <- move(loc, dir=turns[i-1]) # move to the next location according to turns
                positions[i,] <- loc # update positions
                if(loc[1] < hrange[1]) {
                    hrange[1] <- loc[1] # extend the lower bound of hrange if necessary
                } else if(loc[1] > hrange[2]) {
                    hrange[2] <- loc[1] # extend the upper bound of hrange if necessary
                }
                if(loc[2] < vrange[1]) {
                    vrange[1] <- loc[2] # extend the lower bound of vrange if necessary
                } else if(loc[2] > vrange[2]) {
                    vrange[2] <- loc[2] # extend the upper bound of vrange if necessary
                }
            }
        }
        min.pos <- apply(positions, 2, min) # get minimal visited row/column position
        positions <- sweep(positions, 2, min.pos) + 1 # substract these and add (1,1)
        occupancy <- matrix(0, nrow=max(positions[,"x"]), ncol=max(positions[,"y"])) # occupancy matrix; note: already trimmed by construction
        for(i in 1:nrow(positions)) # loop over positions and fill occupancy matrix accordingly
            occupancy[positions[i,1], positions[i,2]] <- switch(turns[i],
                                                                "l" = { 1 },
                                                                "r" = { 2 },
                                                                "d" = { 3 },
                                                                "u" = { 4 },
                                                                stop("Wrong 'turns'"))
        return(list(turns=turns, positions=positions, occupancy=occupancy)) # return here; avoids huge 'else' below
    }

    ## Now consider the case where turns has not been given => construct the path

    ## Checking
    stopifnot(nVars >= 1)
    stopifnot(length(n2dcol) == 1, n2dcol >= 1)
    if(nVars >= 3 && n2dcol < 2)
        stop("If ncol(x) >= 3, n2dcol must be >= 2.")
    method <- match.arg(method)

    ## We start by dealing with three special cases (the same for all methods)
    nPlots <- 2*nVars-1 # total number of plots (1d and 2d)
    path <- if(nPlots <= 3) {
        switch(nPlots,
           { # nPlots = 1
               turns <- "d"
               positions <- matrix(c(1,1), ncol=2, dimnames=list(NULL, c("x", "y")))
               occupancy <- matrix(3, nrow=1, ncol=1)
               list(turns=turns, positions=positions, occupancy=occupancy)
           },
           { # nPlots = 2
               turns <- c("d", "r")
               positions <- matrix(c(1,1, 2,1), ncol=2, byrow=TRUE,
                                   dimnames=list(NULL, c("x", "y")))
               occupancy <- matrix(c(3, 2), nrow=2, ncol=1)
               list(turns=turns, positions=positions, occupancy=occupancy)
           },
           { # nPlots = 3
               turns <- c("d", "r", "r")
               positions <- matrix(c(1,1, 2,1, 2,2), ncol=2, byrow=TRUE,
                                   dimnames=list(NULL, c("x", "y")))
               occupancy <- matrix(c(3,0, 2,2), nrow=2, ncol=2, byrow=TRUE)
               list(turns=turns, positions=positions, occupancy=occupancy)
           },
               stop("Wrong 'nPlots'"))
    } else {

        ## For nPlots >= 4, the repeating order depends on n2dcol
        turns <- character(nPlots) # how we leave every plot (1d or 2d)
        switch(method,
               "double.zigzag" =, "single.zigzag" = {

                   ## Main idea: Determine all turns right away, then build positions and
                   ##            occupancy matrix all from the turns
                   turns <- get_turns_for_zigzag(nPlots, n2dcol=n2dcol, method=method)

                   ## Build positions and occupancy matrix
                   ## Interpretation: 0: not occupied; 1--4: "l", "r", "d", "u"

                   ## Setup
                   ncolOcc <- 2*n2dcol-1 # number of columns in the occupancy matrix
                   occupancy <- matrix(0, nrow=1, ncol=ncolOcc) # occupancy matrix
                   positions <- matrix(0, nrow=nPlots, ncol=2, dimnames=list(NULL, c("x", "y"))) # positions

                   ## Init
                   numeric.turns <- match(turns, table=c("l", "r", "d", "u")) # turns 'turns' into 1--4
                   curpos <- c(1, 1) # current position
                   positions[1,] <- curpos # occupy (row, col)
                   occupancy[curpos[1], curpos[2]] <- numeric.turns[1]

                   ## Loop over all remaining plots
                   maxrowOcc <- 1
                   for(plotNo in 2:nPlots) {
                       ## Update position
                       nextpos <- move(curpos, dir=turns[plotNo-1])
                       positions[plotNo,] <- nextpos # occupy location (row, col)
                       curpos <- nextpos
                       if(curpos[1] > maxrowOcc) { # expand occupancy matrix by one row
                           occupancy <- rbind(occupancy, rep(0, ncolOcc))
                           maxrowOcc <- maxrowOcc + 1
                       }
                       ## Update occupancy matrix
                       occupancy[nextpos[1], nextpos[2]] <- numeric.turns[plotNo]
                   }

                   ## Trim last columns of 0 from occupancy matrix
                   occupancy <- occupancy[,seq_len(max(positions[,2])), drop=FALSE]

                   ## Build path
                   list(turns=turns, positions=positions, occupancy=occupancy)

               },
               "tidy" = {

                   ## Main idea: Build turns, positions and occupancy as we go along
                   ## Interpretation: 0: not occupied; 1--4: "l", "r", "d", "u"
                   ## Note: We could use the (logical!) NA as a third value for 'undetermined yet'

                   ## Setup
                   turns <- character(nPlots) # vector of turns out of current position
                   ncolOcc <- 2*n2dcol+1 # number of columns in the occupancy matrix (2*n2dcol-1 + left/right one more)
                   occupancy <- matrix(0, nrow=1, ncol=ncolOcc) # occupancy matrix
                   positions <- matrix(0, nrow=nPlots, ncol=2, dimnames=list(NULL, c("x", "y"))) # positions

                   ## Init
                   turns[1] <- "d" # turn out of current position
                   positions[1,] <- c(1, 2) # occupy (row, col)
                   occupancy[1, 2] <- 3 # initialize occupancy matrix there (3 = "d")

                   ## Loop over all remaining plots
                   maxrowOcc <- 1
                   for(plotNo in 2:nPlots) {
                       ## Next move
                       nextmove <- next_move_tidy(plotNo-1, nPlots=nPlots,
                                                  curpath=list(turns=turns,
                                                               positions=positions,
                                                               occupancy=occupancy))
                       ## Update turn
                       turns[plotNo] <- nextmove$nextout # nextout = turn out of next position
                       ## Update position
                       nextpos  <- nextmove$nextpos
                       positions[plotNo,] <- nextpos # occupy location (row, col)
                       if(nextpos[1] > maxrowOcc) { # expand occupancy matrix by one row
                           occupancy <- rbind(occupancy, rep(0, ncolOcc))
                           maxrowOcc <- maxrowOcc + 1
                       }
                       ## Update occupancy matrix
                       occupancy[nextpos[1], nextpos[2]] <-
                           switch(turns[plotNo], # update occupancy matrix
                                  "l" = { 1 },
                                  "r" = { 2 },
                                  "d" = { 3 },
                                  "u" = { 4 },
                                  stop("Wrong 'turns' at plotNo ", plotNo))
                   }

                   ## Trim last columns of 0s from occupancy matrix if necessary
                   occupancy <- occupancy[,seq_len(max(positions[,2])), drop=FALSE]
                   ## Trim first column of 0s and adjust positions if necessary
                   if(all(occupancy[,1]==0)) { # first column
                       occupancy <- occupancy[,-1]
                       positions[,2] <- positions[,2] - 1
                   }

                   ## Build path
                   list(turns=turns, positions=positions, occupancy=occupancy)

               },
               stop("Wrong 'method'"))

    }

    ## Trim path if necessary
    if(last1d) path else {
        n <- length(path$turns)
        ## Trim turns
        turns <- path$turns[-n]
        ## Trim occupancy matrix
        occupancy <- path$occupancy
        occupancy[positions[n,1], positions[n,2]] <- 0 # if this produces a 0 column...
        if(all(occupancy[,positions[n,2]]==0)) # ... trim it
            occupancy <- occupancy[,-positions[n,2], drop=FALSE]
        ## Trim positions
        positions <- path$positions[1:(n-1),, drop=FALSE]
        list(turns=turns, positions=positions, occupancy=occupancy)
    }
}

Try the zenplots package in your browser

Any scripts or data that you put into this service are public.

zenplots documentation built on May 2, 2019, 4:34 p.m.