Skip to content

laurasoul/dispeRse

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

dispeRse

This repository is an in development R package intended to generate new models for detecting phylogenetic signals of endemism and dispersal and is supported by two Short-Term Visiting Scholarships from NESCent (to Laura Soul and Graeme Lloyd).

You can install and load dispeRse into R using the following:

# Install the devtools package from CRAN:
install.packages("devtools")

# Load the devtools package into R:
library(devtools)

# Install the dispeRse package from github:
install_github("laurasoul/dispeRse")

# Load the dispeRse package into R:
library(dispeRse)

Script to perform (and plot) a random walk on a sphere:

# Load libraries:
library(dispeRse)
install.packages("sphereplot", dependencies = TRUE)
library(sphereplot)

# Plot spherical grid:
rgl.sphgrid()

# Start at North Pole:
lonlat <- c(0, 90)

# For 1000 random walk steps:
for(i in 1:1000) {

	# Take a random step and store new coordinates:
	lonlat2 <- unlist(EndPoint(lonlat[1], lonlat[2], runif(1, 0, 360), abs(rnorm(1, 0, 100)))[c(1, 2)])

	# Plot coordinates on sphere in rainbow colour order:
	rgl.sphpoints(lonlat2[1], lonlat2[2], 1, deg=TRUE, col=rainbow(1000)[i], cex=2)

	# Update lonlat ready for next step:
	lonlat <- lonlat2

}

Script to perform (and plot) a random walk on a tree on a sphere in continuous time:

# Add and load the Claddis package into R:
library(devtools)
install_github("graemetlloyd/Claddis")
library(Claddis)

# Load libraries:
library(dispeRse)
install.packages(c("ape", "maps"), dependencies = TRUE)
library(ape)
library(maps)

# Generate random 50-taxon tree:
tree <- rtree(10)

# Run function setting start point at equator-Greenwich Meridian intersection:
out <- TreeWalkerContinuous(tree, slon = 0, slat = 0, niter = 100, steplengthmean = 0, steplengthsd = 1000)

# Plot map:
map()

# For each branch:
for(i in 1:length(out)) {

	# Plot walk along branch:
	lines(out[[i]][2, ], out[[i]][3, ], col=rainbow(length(out))[i])

}

Script to perform (and plot) a random walk on a tree on a sphere in discrete time:

# For discrete function
run <- TreeWalkerDiscrete(b = 0.1, d = 0.05, steps = 50, slon = 0, slat = 0, steplengthsd = 1000)

# Plot map:
map()

# For discrete function
for (i in 1:nrow(run$latitudes)) {

    lines(run$longitudes[i, ], run$latitudes[i, ], col = "red")

}

Script to check how different responses to hitting the edge of a continent affect eveness of distribution.

# Load maps library:
library(maps)

# Set Earth radius in kilometres:
EarthRad <- 6367.4447

# Set number of time steps to run simulation for:
niter <- 100

# Set number of animals to run simulation for:
n_aminals <- 10000

# Set standard deviation of step size for dispersal in kilometres:
stepsize_sd <- 100

# Set centre of continent (long, lat):
continent_centre <- c(0, 0)

# Set radius of continent in kilometres:
continent_radius <- 2000

# Create empty matrix to store start positions (long, lat) of each animal:
start_positions <- matrix(nrow = 0, ncol = 2)

# Create vectors to store distances from continent centre at beginning and end of simlation:
end_distances <- start_distances <- vector(mode="numeric")

# Pick a response type (one of "bounce", "redraw", or "stop")
response <- "bounce"

# For each animal:
for(i in 1:n_aminals) {

	# Pick a random start position from the square in which the circular continent sits:
	start_position <- c(runif(1, -continent_radius / ((2 * pi * EarthRad) / 360), continent_radius / ((2 * pi * EarthRad) / 360)), runif(1, -continent_radius / ((2 * pi * EarthRad) / 360), continent_radius / ((2 * pi * EarthRad) / 360)))
	
	# Record distance from centre of continent:
	start_distances[i] <- GreatCircleDistanceFromLongLat(continent_centre[1], continent_centre[2], start_position[1], start_position[2])

	# If the starting point is not on the circular continent:
	if(start_distances[i] > continent_radius) {
	
		# Whilst the starting point is not on the circular continent:
		while(start_distances[i] > continent_radius) {
			
			# Pick a new starting point:
			start_position <- c(runif(1, -continent_radius / ((2 * pi * EarthRad) / 360), continent_radius / ((2 * pi * EarthRad) / 360)), runif(1, -continent_radius / ((2 * pi * EarthRad) / 360), continent_radius / ((2 * pi * EarthRad) / 360)))
			
			# Record distance from centre of continent:
			start_distances[i] <- GreatCircleDistanceFromLongLat(continent_centre[1], continent_centre[2], start_position[1], start_position[2])
			
		}
		
	}
	
	# Store point as start position (now we know it is safely on the continent:
	start_positions <- rbind(start_positions, start_position)
	
}

# Ossify start positions now as they will get overwritten later:
ossified_start_positions <- start_positions

# For each time step:
for(i in 1:niter) {
	
	# For each organism:
	for(j in 1:n_aminals) {
	
		# Draw a random bearing for the next move:
		bearing <- runif(1, 0, 360)
	
		# Draw a random distance for the next move:
		distance <- abs(rnorm(1, 0, stepsize_sd))
		
		# Calculate the new position of the organism following the move:
		new_position <- EndPoint(start_positions[j, 1], start_positions[j, 2], bearing, distance)[c("long", "lat")]

		# If draw means leaving continent:
		if(GreatCircleDistanceFromLongLat(new_position$long, new_position$lat, continent_centre[1], continent_centre[2]) > continent_radius) {
			
			# If response to leaving continent is to redraw:
			if(response == "redraw") {
				
				# As long as the draw removes the taxon from the continent:
				while(GreatCircleDistanceFromLongLat(new_position$long, new_position$lat, continent_centre[1], continent_centre[2]) > continent_radius) {
					
					# Redraw the bearing:
					bearing <- runif(1, 0, 360)
					
					# Redraw the distance:
					distance <- abs(rnorm(1, 0, stepsize_sd))
					
					# Calculate the new position:
					new_position <- EndPoint(start_positions[j, 1], start_positions[j, 2], bearing, distance)[c("long", "lat")]
					
				}
				
				# Overwrite start positions:
				start_positions[j, ] <- unlist(new_position)
				
				# Record distance from centre of continent:
				end_distances[j] <- GreatCircleDistanceFromLongLat(start_positions[j, 1], start_positions[j, 2], continent_centre[1], continent_centre[2])
				
			# If response to leaving continent is not to redraw (i.e., is to bounce off of, or stop at, continent edge):
			} else {
				
# FIND COLLISION POINT STARTS HERE:
				
				# Scalar which describes the proportion of the disatnce the organism has to travel before colliding with the continent edge:
				distance_modifier <- 0.5
				
				# Starting stepsize (will shrink as answer is honed in on more precisely):
				stepsize <- 0.1
				
				# Get starting new position (that will eb overwritten until the collision point is found):
				new_position <- EndPoint(start_positions[j, 1], start_positions[j, 2], bearing, distance_modifier * distance)[c("long", "lat")]
				
				# Whilst the collision point has not been found:
				while(all.equal(as.vector(GreatCircleDistanceFromLongLat(continent_centre[1], continent_centre[2], new_position$long, new_position$lat, EarthRad = EarthRad, Warn = FALSE)), continent_radius) != TRUE) {
					
					# Record current distance of new position from centre of continent:
					current_distance_from_centre <- as.vector(GreatCircleDistanceFromLongLat(continent_centre[1], continent_centre[2], new_position$long, new_position$lat, EarthRad = EarthRad, Warn = FALSE))
					
					# Do we need to increase the distance modifier value?:
					if(current_distance_from_centre < continent_radius) {
						
						# Create potential better value by adding step size to modifier:
						limit <- distance_modifier + stepsize
						
					# Or do we need to decrease the degree modifier value?:
					} else {
						
						# Create potential better value by subtracting step size from modifier:
						limit <- distance_modifier - stepsize
						
					}
					
					# Store the new new position (so we can ask if this is better):
					new_new_position <- EndPoint(start_positions[j, 1], start_positions[j, 2], bearing, limit * distance)[c("long", "lat")]
					
					# Get new distance from continent centre based on potential better modifer:
					new_distance_from_centre <- as.vector(GreatCircleDistanceFromLongLat(continent_centre[1], continent_centre[2], new_new_position$long, new_new_position$lat, EarthRad = EarthRad, Warn = FALSE))

					# If new distance is closer to continent radius:
					if(abs(current_distance_from_centre - continent_radius) > abs(new_distance_from_centre - continent_radius)) {
						
						# Update new position:
						new_position <- new_new_position
						
						# Update distance modifier itself:
						distance_modifier <- limit
						
					# If current distance is stil our best estimate:
					} else {
						
						# Shrink the step size so we can hone in closer:
						stepsize <- stepsize * 0.1
						
					}
					
				}
				
# FIND COLLISION POINT ENDS HERE:

				# If response to hitting a continent edge is to bounce off it:
				if(response == "bounce") {
					
					# Get the remaining distance of the step:
					remaining_distance <- as.vector(distance - GreatCircleDistanceFromLongLat(start_positions[j, 1], start_positions[j, 2], new_position$long, new_position$lat))
					
					# Get the bearing to the centre of the continent from the colision point:
					bearing_to_centre <- as.vector(BearingBetweenTwoLongLatPoints(new_position$long, new_position$lat, continent_centre[1], continent_centre[2]))
					
					# Get the bearing to the start of the dispersal step from the colision point:
					bearing_to_start <- as.vector(BearingBetweenTwoLongLatPoints(new_position$long, new_position$lat, start_positions[j, 1], start_positions[j, 2]))
					
					# Get the difference between these two bearings:
					bearing_difference <- min(c(abs(bearing_to_start - bearing_to_centre), 360 - abs(bearing_to_start - bearing_to_centre)))
					
					# If bearing difference should be subtracted to get new "bounce" bearing:
					if(all.equal((bearing_to_centre + bearing_difference) %% 360, bearing_to_start) == TRUE) {
						
						# Get new "bounce" bearing:
						bearing_to_end <- (bearing_to_centre - bearing_difference) %% 360
						
					# If bearing difference should be added to get new "bounce" bearing:
					} else {
						
						# Get new "bounce" bearing:
						bearing_to_end <- (bearing_to_centre + bearing_difference) %% 360
						
					}
					
					# Record actual new position (after bounce):
					new_position <- EndPoint(new_position$long, new_position$lat, bearing_to_end, remaining_distance)[c("long", "lat")]
					
					# Overwrite start positions:
					start_positions[j, ] <- unlist(new_position)
					
					# Record distance from centre of continent:
					end_distances[j] <- GreatCircleDistanceFromLongLat(start_positions[j, 1], start_positions[j, 2], continent_centre[1], continent_centre[2])
					
					# Little conditional to catch a second collision (does not do what it should, this is just a test to see if it happens):
					if(end_distances[j] > continent_radius) stop("Fallen off after bouncing!")
					
				}
				
				# If response to hitting a continent edge is to stick to it:
				if(response == "stop") {
					
					# Overwrite start positions:
					start_positions[j, ] <- unlist(new_position)
					
					# Record distance from centre of continent:
					end_distances[j] <- GreatCircleDistanceFromLongLat(start_positions[j, 1], start_positions[j, 2], continent_centre[1], continent_centre[2])
					
				}
				
			}
		
		# If draw means staying on continent:
		} else {
			
			# Overwrite start positions:
			start_positions[j, ] <- unlist(new_position)
			
			# Record distance from centre of continent:
			end_distances[j] <- GreatCircleDistanceFromLongLat(start_positions[j, 1], start_positions[j, 2], continent_centre[1], continent_centre[2])
			
		}
		
	}
	
}

# Now make plots to se if there is a bias in animal distribution based on response choice

# Plot map at a size just accommodating the circular continent:
map(xlim=c(-continent_radius / ((2 * pi * EarthRad) / 360), continent_radius / ((2 * pi * EarthRad) / 360)), ylim=c(-continent_radius / ((2 * pi * EarthRad) / 360), continent_radius / ((2 * pi * EarthRad) / 360)))

# Plot start positions in "red":
points(ossified_start_positions[, 1], ossified_start_positions[, 2], pch=19, col="red", cex=0.5)

# Plot end positions in blue:
points(start_positions[, 1], start_positions[, 2], pch=19, col="blue", cex=0.5)

# Create a set of 10 concentric rings to use in determining evenness of animal distribution:
concentric_circles <- seq(0, continent_radius, continent_radius / 10)

# Empty vectors to store N animals in each ring at beginning and end and the area of each ring:
start_N_animals_per_ring <- end_N_animals_per_ring <- sphere_areas <- vector(mode="numeric")

# For each ring:
for(i in 2:length(concentric_circles)) {
	
	# Get the area:
	sphere_areas[(i - 1)] <- SphericalCapArea(concentric_circles[i]) - SphericalCapArea(concentric_circles[(i - 1)])

	# Get the number of animals in the ring at the start of the simulation:
	start_N_animals_per_ring[(i - 1)] <- length(intersect(which(start_distances > concentric_circles[(i - 1)]), which(start_distances <= concentric_circles[i])))
	
	# Get the number of animals in the ring at the end of the simulation:
	end_N_animals_per_ring[(i - 1)] <- length(intersect(which(end_distances > concentric_circles[(i - 1)]), which(end_distances <= concentric_circles[i])))
	
}

# Make a barplot of N animals per area in each ring at the start of the simulation:
barplot(start_N_animals_per_ring / sphere_areas, main="Start")

# Make a barplot of N animals per area in each ring at the end of the simulation:
barplot(end_N_animals_per_ring / sphere_areas, main="End")

Animated results of a dispersal simulation run:

# Load libraries (must have ImageMagick installed too):
library(dispeRse)
library(animation)

# Run a simulation (note, will crash if complete extinction occursso so re-run until it works):
out <- DispersalSimulator(N_steps = 1000, organism_multiplier = 1, radius = 2000, continent_speed_mean = 50, continent_speed_sd = 20, organism_step_sd = 1000, start_configuration = "supercontinent", stickiness = 0)

# Start outputting to GIF:
saveGIF({

	# For each time step:
	for(i in 1:1001) {
		
		# Plot continents:
		MapContinents(cbind(out$continent_positions[, i, 1], out$continent_positions[, i, 2]), radius = 2000)

		# Plot organisms:
		points(x = out$organism_longitudes[, i], y = out$organism_latitudes[, i], pch=19, col="red")

	}

# Additional output information:
}, movie.name = "test.gif", interval = 0.1, nmax = 1001, ani.width = 600, ani.height = 600)

More to follow.

About

Models for paleobiogeograpy

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages