pf_smoother_two_filter()
function implements the two-filter particle smoother (Fearnhead et al., 2010).
Usage
set_vmap(.map = NULL, .mobility = NULL, .vmap = NULL, .plot = FALSE, ...)
pf_smoother_two_filter(
.n_particle = NULL,
.n_sim = 100L,
.cache = TRUE,
.batch = NULL,
.collect = TRUE,
.progress = julia_progress(),
.verbose = getOption("patter.verbose")
)
Arguments
- .map, .mobility, .vmap, .plot, ...
(optional) 'Validity map' arguments for
set_map()
, used for two-dimensional states..map
is aterra::SpatRaster
that defines the study area of the simulation (seepf_filter()
). On Linux, this argument can only be used safely ifJULIA_SESSION = "FALSE"
..mobility
is anumeric
value that defines the maximum moveable distance between two time steps (e.g.,.timeline[1]
and.timeline[2]
inpf_filter()
)..vmap
is aterra::SpatRaster
(supported on Windows or MacOS), or a file path to a raster (supported on MacOS, Windows and Linux), that defines the validity map (seeset_map()
). This can be supplied, from a previous implementation ofset_vmap()
or the internal functionspatVmap()
, instead of.map
and.mobility
to avoid re-computation..plot
is alogical
variable that defines whether or not to plot the map....
is a placeholder for additional arguments, passed toterra::plot()
, if.plot = TRUE
.
The validity map is only set in Julia if
JULIA_SESSION = "TRUE"
.On Linux, the validity map cannot be created and set in the same
R
session. Running the function with.map
and.mobility
will create, but not set, the map. Write the map to file and then rerun the function with.vmap
specified to set the map safely inJulia
.- .n_particle
(optional) An
integer
that defines the number of particles to smooth.If specified, a sub-sample of
.n_particle
s is used.Otherwise,
.n_particle = NULL
uses all particles from the filter.
- .n_sim
An
integer
that defines the number of Monte Carlo simulations.- .cache
A
logical
variable that defines whether or not to pre-compute and cache movement-density normalisation constants for each unique particle.- .batch
(optional) Batching controls:
If
pf_filter()
was implemented with.batch = NULL
, leave.batch = NULL
here.Otherwise,
.batch
must be specified. Pass acharacter
vector of.jld2
file paths to write particles in sequential batches to file (asJulia
Matrix{<:State}
objects) to.batch
; for example:./smo-1.jld2, ./smo-2.jld2, ...
. You must use the same number of batches as inpf_filter()
..batch
is implemented as in that function.
- .collect
A
logical
variable that defines whether or not to collect outputs from theJulia
session inR
.- .progress
Progress controls (see
patter-progress
for supported options). If enabled, one progress bar is shown for each.batch
.- .verbose
User output control (see
patter-progress
for supported options).
Value
set_vmap()
:set_vmap()
returns the validity map (aterra::SpatRaster
), invisibly;
pf_smoother_two_filter()
:Patter.particle_smoother_two_filter()
creates a NamedTuple in theJulia
session (namedptf
). If.batch = NULL
, the NamedTuple contains particles (states
) ; otherwise, thestates
element isnothing
andstates
are written to.jld2
files (as a variable namedxsmo
). If.collect = TRUE
,pf_smoother_two_filter()
collects the outputs inR
as apf_particles
object (thestates
element isNULL
is.batch
is used). Otherwise,invisible(NULL)
is returned.
Details
The two-filter smoother smooths particle samples from the particle filter (pf_filter()
). Particles from a forward and backward filter run are required in the Julia
workspace (as defined by pf_filter()
). The backend function Patter.particle_smoother_two_filter()
does the work. Essentially, the function runs a simulation backwards in time and re-samples particles in line with the probability density of movements between each combination of states from the backward filter at time t
and states from the forward filter at time t - 1
. The time complexity of the algorithm is thus \(O(TN^2)\). The probability density of movements is evaluated by Patter.logpdf_step()
and Patter.logpdf_move()
. If individual states are two-dimensional (see StateXY
), a validity map can be pre-defined in Julia
via set_vmap()
to speed up probability calculations. The validity map is defined as the set of valid (non-NA
or bordering) locations on the .map
, shrunk by .mobility
. Within this region, the probability density of movement between two states can be calculated directly. Otherwise, a Monte Carlo simulation, of .n_sim
iterations, is required to compute the normalisation constant (accounting for movements into inhospitable areas, or beyond the boundaries of the study area). For movement models for which the density only depends on the particle states, set .cache = TRUE
to pre-compute and cache normalisation constants for improved speed.
References
Fearnhead, P. et al. (2010). A sequential smoothing algorithm with linear computational cost. Biometrika 97, 447–464. https://doi.org/10.1093/biomet/asq013.
See also
Particle filters and smoothers sample states (particles) that represent the possible locations of an individual through time, accounting for all data and the individual's movement.
To simulate artificial datasets, see
sim_*()
functions (especiallysim_path_walk()
,sim_array()
andsim_observations()
).To assemble real-world datasets for the filter, see
assemble
_*()
functions.pf_filter()
runs the filter:To run particle smoothing, use
pf_smoother_two_filter()
.To map emergent patterns of space use, use a
map_*()
function (such asmap_pou()
,map_dens()
andmap_hr()
).
Examples
if (patter_run()) {
library(JuliaCall)
#### Set up example
# Set up the particle filter with an example dataset
# (See `?pf_filter()` for the full workflow)
setup <- example_setup("pf_smoother_two_filter")
map <- setup$map
args <- setup$pf_filter_args
# Run the particle filter forwards
args$.direction <- "forward"
fwd <- do.call(pf_filter, args)
# Run the particle filter backwards
args$.direction <- "backward"
bwd <- do.call(pf_filter, args)
#### Example (1): Implement the smoother with default options
# Run the smoother
# * This uses objects defined by `pf_filter()` in `Julia`
# (set_vmap() is explained below)
smo <- pf_smoother_two_filter()
# The filter returns a `pf_particles`-class object
# (See `?pf_filter()` for examples)
class(smo)
summary(smo)
#### Example (2): Implement the smoother using a validity map
# We can use a validity map b/c `.state` = "StateXY"
args$.state
# To define the validity map, define:
# * `.map`
# * `.mobility`, which we can see here is 750 m:
args$.model_move
# Run the smoother
set_vmap(.map = map, .mobility = 750.0, .plot = TRUE)
smo <- pf_smoother_two_filter()
# Reset `vmap` in `Julia` to run the smoother for other state types
# ... in the same R session:
set_vmap()
#### Example (3): Implement the smoother with a sub-sample of particles
# This is useful for quick tests
set_vmap(.map = map, .mobility = 750.0)
smo <- pf_smoother_two_filter(.n_particle = 50L)
#### Example (4): Adjust the number of MC simulations
# set_vmap(.map = map, .mobility = 750.0)
smo <- pf_smoother_two_filter(.n_sim = 1000L)
#### Example (5): Batching workflow for filtering and smoothing
# (Use `.batch` to reduce memory demand)
folder <- file.path(tempdir(), "batch-outputs")
dir.create(folder)
## Run forward filter with batching
batch_fwd <- file.path(folder, c("fwd-1.jld2", "fwd-2.jld2", "fwd-3.jld2"))
args$.direction <- "forward"
args$.batch <- batch_fwd
args$.collect <- TRUE
fwd <- do.call(pf_filter, args)
# In the output, the 'states' element is null
fwd$states
# Other elements are as expected
fwd$callstats
head(fwd$diagnostics)
# Confirm that batch files exist
stopifnot(all(file.exists(batch_fwd)))
## Run backward filter with batching:
batch_bwd <- file.path(folder, c("bwd-1.jld2", "bwd-2.jld2", "bwd-3.jld2"))
args$.direction <- "backward"
args$.batch <- batch_bwd
bwd <- do.call(pf_filter, args)
summary(bwd)
stopifnot(all(file.exists(batch_bwd)))
## Run smoothing with batching
# set_vmap(.map = map, .mobility = 750.0)
batch_smo <- file.path(folder, c("smo-1.jld2", "smo-2.jld2", "smo-3.jld2"))
smo <- pf_smoother_two_filter(.batch = batch_smo)
summary(smo)
stopifnot(all(file.exists(batch_smo)))
## Collate outputs in R
julia_command('
smo_states = hcat([f["xsmo"] for f in map(jldopen, batch_smo)]...);')
smo$states <- julia_eval('
Patter.r_get_states(smo_states, collect(1:length(timeline)), timeline);
')
head(smo$states)
#### Example (6): Analyse smoothed particles
# * See `map_*()` functions (e.g., `?map_dens()`) to map utilisation distributions
# Cleanup
file_cleanup(folder)
set_vmap()
}
#> `patter::julia_connect()` called @ 2025-04-22 09:31:41...
#> ... Running `Julia` setup via `JuliaCall::julia_setup()`...
#> ... Validating Julia installation...
#> ... Setting up Julia project...
#> ... Handling dependencies...
#> ... `Julia` set up with 11 thread(s).
#> `patter::julia_connect()` call ended @ 2025-04-22 09:31:41 (duration: ~0 sec(s)).
#> `patter::pf_filter()` called @ 2025-04-22 09:31:41...
#> `patter::pf_filter_init()` called @ 2025-04-22 09:31:41...
#> ... 09:31:41: Setting initial states...
#> ... 09:31:41: Setting observations dictionary...
#> `patter::pf_filter_init()` call ended @ 2025-04-22 09:31:41 (duration: ~0 sec(s)).
#> ... 09:31:41: Running filter...
#> Message: On iteration 1 ...
#>
#> Message: Running filter for batch 1 / 1 ...
#>
#> ... 09:31:42: Collating outputs...
#> `patter::pf_filter()` call ended @ 2025-04-22 09:31:42 (duration: ~1 sec(s)).
#> `patter::pf_filter()` called @ 2025-04-22 09:31:42...
#> `patter::pf_filter_init()` called @ 2025-04-22 09:31:42...
#> ... 09:31:42: Setting initial states...
#> ... 09:31:42: Setting observations dictionary...
#> `patter::pf_filter_init()` call ended @ 2025-04-22 09:31:42 (duration: ~0 sec(s)).
#> ... 09:31:42: Running filter...
#> Message: On iteration 1 ...
#>
#> Message: Running filter for batch 1 / 1 ...
#>
#> ... 09:31:42: Collating outputs...
#> `patter::pf_filter()` call ended @ 2025-04-22 09:31:42 (duration: ~0 sec(s)).
#> `patter::pf_smoother_two_filter()` called @ 2025-04-22 09:31:42...
#> ... 09:31:42: Running smoother...
#> Message: Running smoother for batch 1 / 1 ...
#>
#> Message: Precomputing normalisation constants for batch ...
#>
#> Message: Smoothing ...
#>
#> ... 09:31:43: Collating outputs...
#> `patter::pf_smoother_two_filter()` call ended @ 2025-04-22 09:31:43 (duration: ~1 sec(s)).
#> `patter::pf_smoother_two_filter()` called @ 2025-04-22 09:31:43...
#> ... 09:31:43: Running smoother...
#> Message: Running smoother for batch 1 / 1 ...
#>
#> Message: Precomputing normalisation constants for batch ...
#>
#> Message: Smoothing ...
#>
#> ... 09:31:44: Collating outputs...
#> `patter::pf_smoother_two_filter()` call ended @ 2025-04-22 09:31:44 (duration: ~1 sec(s)).
#> `patter::pf_smoother_two_filter()` called @ 2025-04-22 09:31:44...
#> ... 09:31:44: Running smoother...
#> Message: Running smoother for batch 1 / 1 ...
#>
#> Message: Precomputing normalisation constants for batch ...
#>
#> Message: Smoothing ...
#>
#> ... 09:31:44: Collating outputs...
#> `patter::pf_smoother_two_filter()` call ended @ 2025-04-22 09:31:44 (duration: ~0 sec(s)).
#> `patter::pf_smoother_two_filter()` called @ 2025-04-22 09:31:44...
#> ... 09:31:44: Running smoother...
#> Message: Running smoother for batch 1 / 1 ...
#>
#> Message: Precomputing normalisation constants for batch ...
#>
#> Message: Smoothing ...
#>
#> ... 09:31:44: Collating outputs...
#> `patter::pf_smoother_two_filter()` call ended @ 2025-04-22 09:31:44 (duration: ~0 sec(s)).
#> `patter::pf_filter()` called @ 2025-04-22 09:31:44...
#> `patter::pf_filter_init()` called @ 2025-04-22 09:31:44...
#> ... 09:31:44: Setting initial states...
#> ... 09:31:44: Setting observations dictionary...
#> `patter::pf_filter_init()` call ended @ 2025-04-22 09:31:44 (duration: ~0 sec(s)).
#> ... 09:31:44: Running filter...
#> Message: On iteration 1 ...
#>
#> Message: Running filter for batch 1 / 3 ...
#>
#> Message: Writing particles to disk ...
#>
#> Message: Running filter for batch 2 / 3 ...
#>
#> Message: Writing particles to disk ...
#>
#> Message: Running filter for batch 3 / 3 ...
#>
#> Message: Writing particles to disk ...
#>
#> ... 09:31:46: Collating outputs...
#> `patter::pf_filter()` call ended @ 2025-04-22 09:31:46 (duration: ~2 sec(s)).
#> `patter::pf_filter()` called @ 2025-04-22 09:31:46...
#> `patter::pf_filter_init()` called @ 2025-04-22 09:31:46...
#> ... 09:31:46: Setting initial states...
#> ... 09:31:46: Setting observations dictionary...
#> `patter::pf_filter_init()` call ended @ 2025-04-22 09:31:46 (duration: ~0 sec(s)).
#> ... 09:31:46: Running filter...
#> Message: On iteration 1 ...
#>
#> Message: Running filter for batch 1 / 3 ...
#>
#> Message: Writing particles to disk ...
#>
#> Message: Running filter for batch 2 / 3 ...
#>
#> Message: Writing particles to disk ...
#>
#> Message: Running filter for batch 3 / 3 ...
#>
#> Message: Writing particles to disk ...
#>
#> ... 09:31:46: Collating outputs...
#> `patter::pf_filter()` call ended @ 2025-04-22 09:31:46 (duration: ~0 sec(s)).
#> `patter::pf_smoother_two_filter()` called @ 2025-04-22 09:31:46...
#> ... 09:31:46: Running smoother...
#> Message: Running smoother for batch 1 / 3 ...
#>
#> Message: Reading particles from disk ...
#>
#> Message: Precomputing normalisation constants for batch ...
#>
#> Message: Smoothing ...
#>
#> Message: Writing smoothed particles to disk ...
#>
#> Message: Running smoother for batch 2 / 3 ...
#>
#> Message: Reading particles from disk ...
#>
#> Message: Precomputing normalisation constants for batch ...
#>
#> Message: Smoothing ...
#>
#> Message: Writing smoothed particles to disk ...
#>
#> Message: Running smoother for batch 3 / 3 ...
#>
#> Message: Reading particles from disk ...
#>
#> Message: Precomputing normalisation constants for batch ...
#>
#> Message: Smoothing ...
#>
#> Message: Writing smoothed particles to disk ...
#>
#> ... 09:31:47: Collating outputs...
#> `patter::pf_smoother_two_filter()` call ended @ 2025-04-22 09:31:47 (duration: ~1 sec(s)).