|
| 1 | +########################### |
| 2 | +########################### |
| 3 | +#### patter-analyses |
| 4 | + |
| 5 | +#### Aims |
| 6 | +# 1) Run patter analyses |
| 7 | + |
| 8 | +#### Prerequisites |
| 9 | +# 1) patter-setup.R |
| 10 | + |
| 11 | + |
| 12 | +########################### |
| 13 | +########################### |
| 14 | +#### Setup |
| 15 | + |
| 16 | +#### Wipe workspace |
| 17 | +rm(list = ls()) |
| 18 | +# try(pacman::p_unload("all"), silent = TRUE) |
| 19 | +dv::clear() |
| 20 | + |
| 21 | +#### Essential packages |
| 22 | +library(data.table) |
| 23 | +library(dtplyr) |
| 24 | +library(dplyr, warn.conflicts = FALSE) |
| 25 | +library(JuliaCall) |
| 26 | +library(lubridate) |
| 27 | +library(patter) |
| 28 | +library(tictoc) |
| 29 | + |
| 30 | +#### Load data |
| 31 | +champlain <- terra::vect("data/ChamplainRegions.shp") |
| 32 | +moorings <- readRDS("data/moorings.rds") |
| 33 | +detections <- readRDS("data/simulated_detections.rds") |
| 34 | +metadata <- readRDS("data/simulations_metadata.rds") |
| 35 | +dv::src() |
| 36 | + |
| 37 | +#### Julia setup |
| 38 | +julia_connect() |
| 39 | +set_seed() |
| 40 | +set_map() |
| 41 | + |
| 42 | +########################### |
| 43 | +########################### |
| 44 | +#### Prepare algorithms |
| 45 | + |
| 46 | + |
| 47 | +########################### |
| 48 | +#### Individual datasets |
| 49 | + |
| 50 | +#### Define individual & associated datasets |
| 51 | +id <- 1 |
| 52 | +sid <- paste0("sim_", id) |
| 53 | +path <- paths[sim_id == id, ] |
| 54 | +dets <- detections[sim_id == sid, ] |
| 55 | +meta <- metadata[sim_id == sid, ] |
| 56 | +stopifnot(nrow(meta) == 1L) |
| 57 | + |
| 58 | + |
| 59 | +########################### |
| 60 | +#### Define study timeline |
| 61 | + |
| 62 | +# The timeline is individual specific |
| 63 | +# Start time: "2022-01-01 00:00:00" |
| 64 | +# Simulated tracks comprised 5000 steps |
| 65 | +# Each step comprised 500 m |
| 66 | +# But velocity set to different values in transmit_along_path() |
| 67 | +# I.e., For each individual, the duration of a step length is different |
| 68 | +# Transmissions were generated along the paths every 120 + 7 s |
| 69 | + |
| 70 | +# Calculate the step duration |
| 71 | +trms_time <- 120 # + 7 # transmission time (every 127 s) |
| 72 | +mvt_speed <- meta$velocity # speed @ each _movement_ time step (m/s) |
| 73 | +mvt_distance <- 500 # distance moved at each _movement_ time step (m) |
| 74 | +mvt_time <- mvt_distance / mvt_speed # duration of each _movement_ time step (s) |
| 75 | + |
| 76 | +# Define simulation start and end times |
| 77 | +end <- max(seq(start, length.out = 5000, by = paste(mvt_time, "secs"))) |
| 78 | + |
| 79 | +# Define timeline |
| 80 | +# * The timeline is based on a transmission interval of ~2 mins |
| 81 | +# * (The step length (m) in ~2 mins given {mvt_speed} is {mvt_speed} * {trms_time}) |
| 82 | +# * (The angle is simulated every {mvt_time} s) |
| 83 | +timeline <- seq.POSIXt(start, end, by = paste(trms_time, "secs")) |
| 84 | + |
| 85 | +# Validate that the timeline spans the detection time series for the ID |
| 86 | +stopifnot(interval(min(dets$timestamp), max(dets$timestamp)) %within% interval(min(timeline), max(timeline))) |
| 87 | + |
| 88 | + |
| 89 | +########################### |
| 90 | +#### Movement model |
| 91 | + |
| 92 | +#### Movement & timeline validation |
| 93 | +# Check simulated step lengths and turning angles at 127 s resolution |
| 94 | +# * The path was simulated with 5000 steps, each of {mvt_time} secs |
| 95 | +# * complete_simulated_transmissions_regions.rds contains the 'full' path @ resolution of 127 s |
| 96 | +# * Step lengths should match {mvt_speed} (m/s) * {trms_time} (s) |
| 97 | +mvt_len <- mvt_speed * trms_time |
| 98 | +if (FALSE) { |
| 99 | + movements <- |
| 100 | + path |> |
| 101 | + lazy_dt() |> |
| 102 | + select(sim_id, date = timestamp, x, y) |> |
| 103 | + as.data.frame() |> |
| 104 | + bayesmove::prep_data(coord.names = c("x", "y"), id = "sim_id") |
| 105 | + unique(diff(movements$date)) # time stamps evenly spaced |
| 106 | + unique(movements$step) # step lengths not exactly equal |
| 107 | + hist(movements$step) # but most step lengths are correct |
| 108 | + unique(movements$angle) # angles (radians) |
| 109 | +} |
| 110 | +# Check distance of individual to receiver @ moment of detection |
| 111 | +# TO DO |
| 112 | + |
| 113 | +#### Define movement model |
| 114 | +# Paths were simulated via glatos::crw_in_polygon() which calls glatos::crw() |
| 115 | +# * https://github.com/ocean-tracking-network/glatos/blob/main/R/sim-crw_in_polygon.r |
| 116 | +# * https://github.com/ocean-tracking-network/glatos/blob/main/R/simutil-crw.r |
| 117 | +# Step length in {trms_time} given {mvt_speed} is {mvt_speed} (m/s) * {trms_time} (s) |
| 118 | +# heading_{t = 1} = Uniform(0, 360) |
| 119 | +# heading_{t > 1} = Normal(0, meta$theta) + cumsum(heading{t = 1, ..., t}) |
| 120 | +# Angles updated every {mvt_time} (s) / {trms_time} (s) time steps |
| 121 | +# (Internally glatos enlarges the SD if the simulation steps outside the polygon!) |
| 122 | +state <- "StateXYD" |
| 123 | +model_move <- move_xyd(length = mvt_len, theta = meta$theta) |
| 124 | +update_angle_every <- round(mvt_time / trms_time) |
| 125 | +julia_assign("update_angle_every", update_angle_every) |
| 126 | +julia_command('include("Julia/src/model-movement.jl");') |
| 127 | + |
| 128 | + |
| 129 | +########################### |
| 130 | +#### Define observation model |
| 131 | + |
| 132 | +model_obs <- c("ModelObsAcousticLogisTrunc", "ModelObsAcousticContainer") |
| 133 | +acoustics <- assemble_acoustics(.timeline = timeline, .acoustics = dets, .moorings = moorings) |
| 134 | +containers <- assemble_acoustics_containers(.acoustics = acoustics, |
| 135 | + .direction = "forward", |
| 136 | + .mobility = mvt_len, |
| 137 | + .threshold = ydist) |
| 138 | +yobs <- list(ModelObsAcousticLogisTrunc = acoustics, |
| 139 | + ModelObsAcousticContainer = containers) |
| 140 | + |
| 141 | + |
| 142 | +########################### |
| 143 | +########################### |
| 144 | +#### Algorithm runs |
| 145 | + |
| 146 | +#### Define filter arguments |
| 147 | +args <- list(.map = map, |
| 148 | + .timeline = timeline, |
| 149 | + .state = state, |
| 150 | + .xinit = NULL, |
| 151 | + .xinit_pars = list(mobility = mvt_len), |
| 152 | + .yobs = yobs, |
| 153 | + .model_obs = model_obs, |
| 154 | + .model_move = model_move, |
| 155 | + .n_particle = 10000L, |
| 156 | + .direction = "forward" |
| 157 | + ) |
| 158 | + |
| 159 | + |
| 160 | +#### Run forward filter |
| 161 | +# * NB: Setting observations is slow (1 min) |
| 162 | +# > Weights from filter (1 -> 208292) are zero at time 62367 |
| 163 | +fwd <- do.call(pf_filter, args, quote = TRUE) |
| 164 | + |
| 165 | +#### Run backward filter |
| 166 | +# * Update containers & yobs for .direction = "backward", then run filter |
| 167 | +containers <- assemble_acoustics_containers(.acoustics = acoustics, |
| 168 | + .direction = "backward", |
| 169 | + .mobility = distance, |
| 170 | + .threshold = ydist) |
| 171 | +yobs <- list(ModelObsAcousticLogisTrunc = acoustics, |
| 172 | + ModelObsAcousticContainer = containers) |
| 173 | +args$.yobs <- yobs |
| 174 | +args$.direction <- "backward" |
| 175 | +do.call(pf_filter, args, quote = TRUE) |
| 176 | + |
| 177 | +#### Run smoother |
| 178 | +out_smo <- pf_smoother_two_filter() |
| 179 | + |
| 180 | + |
| 181 | +########################### |
| 182 | +########################### |
| 183 | +#### Quick residency analysis |
| 184 | + |
| 185 | +#### True residency in each region |
| 186 | +path_res <- |
| 187 | + path |> |
| 188 | + group_by(region) |> |
| 189 | + summarise(n = n()) |> |
| 190 | + mutate(residency = (n / sum(n)) * 100, |
| 191 | + estimate = "path", |
| 192 | + sim_id = id) |> |
| 193 | + as.data.table() |
| 194 | + |
| 195 | +#### Particle residency estimates |
| 196 | +part_res <- |
| 197 | + out_smo |> |
| 198 | + mutate(region = terra::extract(map, cbind(x, y))) |> |
| 199 | + group_by(region) |> |
| 200 | + mutate(residency = (n / sum(n)) * 100, |
| 201 | + estimate = "smoother", |
| 202 | + sim_id = id) |> |
| 203 | + as.data.table() |
| 204 | + |
| 205 | +#### Collect data |
| 206 | +res <- rbind(path_res, part_res) |
| 207 | +qs::qsave(res, "data/patter/output/residency/qresidency/", paste0(id, ".qs")) |
| 208 | + |
| 209 | + |
| 210 | +########################### |
| 211 | +########################### |
| 212 | +#### Synthesis |
| 213 | + |
| 214 | +# Read residency data for each individual |
| 215 | +residency <- |
| 216 | + lapply(unique(paths$sim_id), function(id) { |
| 217 | + qs::qread("data/patter/qresidency.qs") |
| 218 | +}) |> rbindlist() |
| 219 | + |
| 220 | +# Visualise residency ~ individual, coloured by truth/algorithm |
| 221 | +# |
| 222 | +# > TO DO |
| 223 | + |
| 224 | + |
| 225 | +#### End of code. |
| 226 | +########################### |
| 227 | +########################### |
0 commit comments