ResistanceGA2 supports two distance engines:
gdistance (which runs entirely inside R) and Circuitscape
running through Julia (which requires an external Julia installation).
This vignette covers everything needed to install, configure, and
troubleshoot the Julia/Circuitscape path.
When to use Julia/Circuitscape vs. gdistance:
| Criterion | Julia/Circuitscape | gdistance |
|---|---|---|
| Output matches Circuitscape | Yes | No |
| Current maps | Yes | No |
| Speed (iterative optimization) | Slower per call (startup overhead) | Faster per call |
| External dependency | Julia + Circuitscape.jl | None |
| Random-walk distances | Yes (resistance distance) | Yes (commuteDistance) |
| Least-cost path distances | No | Yes (costDistance) |
For most iterative optimization workflows,
gdistance::commuteDistance() is the most convenient choice
as it stays inside R, requires no external setup, and produces
qualitatively similar distances to Circuitscape resistance distance. Use
Julia/Circuitscape when you need to:
Download Julia from the official website: https://julialang.org/downloads/
Install the current stable release (e.g., Julia 1.11.x). Both 64-bit Windows and macOS/Linux installers are available. The default installation location works well; note the installation path because you will need it later.
Version compatibility. The R-to-Julia bridge depends on the version of
JuliaConnectoRyou have installed keeping pace with very new Julia releases. IfJuliaConnectoR::juliaEval()crashes the R session even thoughJuliaConnectoR::juliaSetupOk()returnsTRUE, the most likely cause is a mismatch between a bleeding-edge Julia release and an olderJuliaConnectoR. In that case, updateJuliaConnectoRto its latest version, or install a Julia release one minor version behind the newest. A combination known to work for the package developer should be preferred over the absolute newest Julia.
Windows users. Do not rely on the
juliacommand installed through the Microsoft Store app alias. That stub can report a version withjulia --versionyet fail to launch a working session forJuliaConnectoR(you may see an error about a missingsys.dll). Install Julia from the official downloads page above and pointJULIA_HOMEat that installation’sbin/directory.
Default installation paths by platform:
C:\Users\<username>\AppData\Local\Programs\Julia-X.Y.Z\/Applications/Julia-X.Y.app/Contents/Resources/julia//opt/julia-X.Y.Z/The Julia binary directory (bin/) is one level inside
the installation root. For example, on Windows:
C:\Users\<username>\AppData\Local\Programs\Julia-<version>\bin\
Open a terminal (Command Prompt, PowerShell, or bash) and run:
You should see output like julia version 1.x.y. If the
command is not found, Julia’s bin/ directory is not on your
PATH. You may need to add it manually, or just supply the full path when
configuring ResistanceGA2.
Open Julia (type julia at the terminal prompt, or open
the Julia application) and install the Circuitscape package:
The installation will download Circuitscape and its dependencies. The test suite confirms the installation is working correctly. This step only needs to be done once per Julia environment.
Note: Julia has a package manager that maintains environments similar to R’s
renvor Python’svenv. By default, packages you install from the Julia REPL go into the default environment (@v1.11for Julia 1.11). WhenResistanceGA2launches Julia viaJuliaConnectoR, it uses this same default environment. If you later switch Julia versions, you will need to install Circuitscape again in the new version’s default environment.
ResistanceGA2 uses JuliaConnectoR as its
R-to-Julia bridge. This replaces the JuliaCall package used
in the original ResistanceGA. If your scripts call
JuliaCall functions directly, update them to the
JuliaConnectoR equivalents.
Install the JuliaConnectoR R package:
ResistanceGA2 needs to know where Julia’s
bin/ directory is. Set this before calling any
Julia-related functions:
# Replace <version> with the installed Julia version.
JULIA_HOME <- "C:/Users/your-name/AppData/Local/Programs/Julia-<version>/bin/"
Sys.setenv(JULIA_BINDIR = JULIA_HOME)To avoid setting this every session, add the
Sys.setenv() line to your .Rprofile:
# Add to ~/.Rprofile (or project-level .Rprofile):
Sys.setenv(JULIA_BINDIR = "C:/Users/your-name/AppData/Local/Programs/Julia-<version>/bin/")Note: On Windows, use forward slashes (
/) in path strings in R, even though Windows normally uses backslashes.JuliaConnectoRandResistanceGA2normalize paths internally, but using forward slashes avoids escape-sequence issues.
The Julia-facing helpers in ResistanceGA2 are designed
to keep your R session stable while still making Julia configuration
explicit. You can either set JULIA_BINDIR once for the
whole session, or pass the same path as JULIA_HOME to
jl.prep(), Run_CS.jl(), or
Run_CS.ini().
When you pass JULIA_HOME, the package temporarily points
JuliaConnectoR to that Julia bin/ directory
and restores the previous JULIA_BINDIR value when the
function exits. This matters when you work on a machine with multiple
Julia versions, or when a project should use a specific Julia
installation without leaving your global R session changed.
# Record the session state before configuring the ResistanceGA2 Julia backend.
old_bindir <- Sys.getenv("JULIA_BINDIR", unset = NA_character_)
JULIA_HOME <- "C:/Users/your-name/AppData/Local/Programs/Julia-<version>/bin/"
jl_inputs <- jl.prep(
n.Pops = nrow(samples),
response = lower(as.matrix(dist(samples[, 2:3]))),
CS_Point.File = terra::vect(samples[, 2:3], type = "points"),
JULIA_HOME = JULIA_HOME,
run_test = FALSE
)
# The package restores the previous value after jl.prep() returns.
identical(Sys.getenv("JULIA_BINDIR", unset = NA_character_), old_bindir)ResistanceGA2 also loads Circuitscape.jl
once per R session and Julia bin/ path. The first call to
jl.prep(), Run_CS.jl(), or
Run_CS.ini() may still pay the Julia startup cost, but
later calls with the same JULIA_HOME reuse the loaded Julia
session.
How to read this setup: JULIA_HOME is a
project-level choice. It tells the package which Julia installation
should run Circuitscape. JULIA_BINDIR is the environment
variable used by JuliaConnectoR internally. In normal
ResistanceGA2 workflows, pass JULIA_HOME to
the package function and let the helper manage JULIA_BINDIR
for that call.
If Julia is on your system PATH, you can find the bin/
directory from R:
# Find Julia on PATH
julia_exe <- Sys.which("julia")
if (nzchar(julia_exe)) {
JULIA_HOME <- dirname(julia_exe)
message("Found Julia at: ", JULIA_HOME)
} else {
message("Julia not found on PATH. Set JULIA_BINDIR manually.")
}After setting JULIA_BINDIR, verify that R can start
Julia and load Circuitscape:
Sys.setenv(JULIA_BINDIR = JULIA_HOME)
# This triggers Julia startup on first call
JuliaConnectoR::juliaEval("string(VERSION)")These verification chunks are shown for copy-paste use but are not
executed during vignette builds. Run them manually in your own R session
after setting JULIA_BINDIR.
If both commands succeed without error, your Julia/Circuitscape setup
is ready for ResistanceGA2.
pts <- vect(samples[, 2:3], type = "points")
surfaces <- terra::unwrap(resistance_surfaces)
surface <- terra::subset(surfaces, "continuous")
pts
#> class : SpatVector
#> geometry : points
#> dimensions : 25, 0 (geometries, attributes)
#> extent : 6.25, 19.25, 6.25, 19.25 (xmin, xmax, ymin, ymax)
#> coord. ref. :
surface
#> class : SpatRaster
#> size : 50, 50, 1 (nrow, ncol, nlyr)
#> resolution : 0.5, 0.5 (x, y)
#> extent : 0, 25, 0, 25 (xmin, xmax, ymin, ymax)
#> coord. ref. :
#> source(s) : memory
#> name : continuous
#> min value : 6.514823
#> max value : 13.376758jl.prep() configures everything needed for Circuitscape
runs. Setting run_test = TRUE is strongly recommended the
first time you use the package on a new machine; it runs a small
Circuitscape test to verify the full pipeline before a long
optimization:
pairwise_genetic <- lower(as.matrix(dist(samples[, 2:3])))
jl_inputs <- jl.prep(
n.Pops = nrow(samples),
response = pairwise_genetic,
CS_Point.File = pts,
JULIA_HOME = JULIA_HOME,
run_test = TRUE, # verify the connection
silent = TRUE # suppress Julia startup messages
)
names(jl_inputs)| Parameter | Default | Description |
|---|---|---|
n.Pops |
required | Number of sample populations |
response |
NULL | Pairwise genetic distances (vector from lower()) |
CS_Point.File |
required | SpatVector of sample point locations |
JULIA_HOME |
required | Path to Julia bin/ directory |
run_test |
FALSE | Run a test Circuitscape calculation to verify setup |
silent |
FALSE | Suppress Julia startup messages |
cholmod |
TRUE | Use CHOLMOD direct solver (faster, more memory) |
precision |
FALSE | Use single precision (saves memory for large landscapes) |
scratch |
NULL | Temporary file directory (use if default tmpdir is slow) |
rm.files |
TRUE | Remove intermediate files after each run |
Solver selection guidance:
cholmod = TRUE (default): Uses the CHOLMOD Cholesky
factorization. Fastest option but can require substantial memory for
large rasters. Start here.cholmod = FALSE (iterative): Falls back to iterative
methods. Slower but uses less peak memory. Try this if CHOLMOD runs out
of memory.precision = TRUE: Uses single-precision arithmetic,
roughly halving memory use. Slightly less accurate but acceptable for
most landscape genetics analyses.In this workflow, Run_CS.jl(..., output = "matrix")
returns a lower-triangle vector of pairwise resistance distances,
ordered the same way as other ResistanceGA2 pairwise
outputs. That is why the result is indexed like a vector, not like a 2-D
matrix.
cs_vector <- Run_CS.jl(
jl.inputs = jl_inputs,
r = surface,
output = "matrix"
)
length(cs_vector)
head(cs_vector)The precomputed example for the small dataset ships with the package:
.ini-style controlIf you want fuller control over Circuitscape options than
Run_CS.jl() is designed to expose, use
Run_CS.ini().
cs_out <- Run_CS.ini(
r = surface,
CS_Point.File = pts,
JULIA_HOME = JULIA_HOME,
scenario = "pairwise",
connect_four_neighbors_only = TRUE,
write_cur_maps = TRUE,
write_cum_cur_map_only = TRUE,
return = "all",
rm.files = FALSE
)See the separate Direct Circuitscape Runs from R
vignette for examples using advanced mode, source and ground inputs,
included-pairs files, and other Circuitscape support files.
| Parameter | Default | Description |
|---|---|---|
jl.inputs |
required | Output of jl.prep() |
r |
required | SpatRaster resistance surface |
CurrentMap |
FALSE | Compute cumulative current map in addition to pairwise distances |
output |
"matrix" |
"matrix" for pairwise distances; "raster"
for current map |
EXPORT.dir |
NULL | Directory for output files when output = "raster" |
rm.files |
TRUE | Remove temporary files after run |
write.files |
FALSE | Write output files even when temporary |
write.criteria |
NULL | Only write files when LL exceeds this threshold |
Current maps show the cumulative flow of “current” across the entire landscape between all pairs of source nodes. They are useful for identifying high-connectivity corridors, bottlenecks, and pinch points.
export_dir <- file.path(tempdir(), "ResistanceGA2-current-map")
current_map <- Run_CS.jl(
jl.inputs = jl_inputs,
r = surface,
CurrentMap = TRUE,
output = "raster",
EXPORT.dir = export_dir
)
plot(current_map, main = "Cumulative current map")The package ships a precomputed current map for the small example:
Once jl_inputs is ready, the optimization workflow is
identical to the gdistance workflow: simply pass
jl.inputs instead of gdist.inputs:
GA.inputs <- GA.prep(
raster = terra::subset(terra::unwrap(resistance_surfaces), "continuous"),
Results.dir = file.path(tempdir(), "ResistanceGA2-julia"),
pop.size = 25,
maxiter = 200,
run = 25,
monitor = FALSE,
quiet = TRUE
)
ss_out <- SS_optim(
jl.inputs = jl_inputs,
GA.inputs = GA.inputs,
dist_mod = FALSE,
null_mod = FALSE,
diagnostic_plots = FALSE
)These optimization outputs are now S3 objects, so routine inspection is cleaner than before.
For multiple surfaces:
GA.inputs_multi <- GA.prep(
raster = terra::subset(
terra::unwrap(resistance_surfaces),
c("categorical", "continuous")
),
Results.dir = file.path(tempdir(), "ResistanceGA2-julia-multi"),
pop.size = 25,
maxiter = 200,
run = 25,
monitor = FALSE,
quiet = TRUE
)
ms_out <- MS_optim(
jl.inputs = jl_inputs,
GA.inputs = GA.inputs_multi,
diagnostic_plots = FALSE
)Note: During optimization,
ResistanceGA2automatically suppresses the verbose@infoand@warnoutput that Circuitscape.jl prints to the console. You will not see per-call messages from Julia duringSS_optim(),MS_optim(), orall_comb()runs. Setsilent = TRUEinjl.prep()to also suppress Julia startup messages during the initial connection test.
Tip: During optimization,
ResistanceGA2callsRun_CS.jl()hundreds or thousands of times (once per candidate solution per generation). Julia’s startup overhead is paid only once whenJuliaConnectoRfirst connects; subsequent calls are much faster. However, file I/O for the intermediate Circuitscape inputs and outputs still occurs each call. Settingrm.files = TRUE(the default) keeps the temporary directory clean and avoids accumulating large numbers of files.
Both gdistance::commuteDistance() and Circuitscape
compute random-walk based effective distances. They are derived from the
same mathematical framework (circuit theory / random walk theory) and
should produce highly correlated outputs on the same resistance
surface.
In practice, the outputs are not identical because:
commuteDistance() uses a graph-based representation
where only adjacent cells are connected, while Circuitscape models
current flow through the full grid using finite element methods.commuteDistance() uses 4-neighbor or 8-neighbor
connectivity; Circuitscape uses 4-neighbor by default.The two distance measures will typically be very highly correlated (r > 0.95) for the same resistance surface, but the absolute values will differ in scale.
# Compute both distance types from the same surface
gdist_inputs <- gdist.prep(
n.Pops = nrow(samples),
samples = terra::vect(samples[, 2:3], type = "points"),
method = "commuteDistance"
)
gdist_out <- Run_gdistance(
gdist.inputs = gdist_inputs,
r = terra::subset(terra::unwrap(resistance_surfaces), "continuous")
)
# These will be on different scales but should be highly correlated
gdist_vec <- lower(as.matrix(gdist_out))
cs_vec <- vignette_pairwise_outputs$circuitscape_matrix
cor(gdist_vec, cs_vec)
# Visual comparison
plot(gdist_vec, cs_vec,
xlab = "gdistance commuteDistance",
ylab = "Circuitscape resistance distance",
main = "Comparison of distance engines")The high correlation means that optimization results (which surface is best, what parameters are recovered) are typically very similar between the two engines. The choice of distance engine should be driven by what you need from the output (Circuitscape-matched values, current maps, etc.), not by expectation of meaningfully different optimization results.
Circuitscape computational cost scales approximately with the number of non-null cells in the resistance raster. Larger landscapes take longer.
| Raster size (cells) | Approximate runtime per call | Notes |
|---|---|---|
| < 50,000 | < 1 second | Negligible overhead |
| 50,000 – 500,000 | 1–30 seconds | Optimization may be slow |
| > 500,000 | 30+ seconds | Consider aggregating raster |
| > 2,000,000 | Minutes per call | Impractical for iterative optimization |
For large landscapes, consider:
gdistance for the iterative
optimization step, then running Circuitscape once with the optimal
parameters for final output.precision = TRUE to reduce
memory use.cholmod = FALSE if CHOLMOD runs
out of memory.Circuitscape runtime also scales with the number of source nodes (sample populations). For n populations, there are n*(n-1)/2 pairwise computations.
| n populations | Pairwise observations | Typical overhead per call |
|---|---|---|
| 10 | 45 | Minimal |
| 20 | 190 | Moderate |
| 50 | 1225 | Substantial |
| 100 | 4950 | Consider gdistance instead |
For datasets with many populations on large landscapes,
gdistance will usually be substantially faster for
optimization. Use Circuitscape for the final output only.
recommend_commute_settings() can help with that handoff.
A common pattern is:
commute_plan <- recommend_commute_settings(
raster = terra::subset(terra::unwrap(resistance_surfaces), "continuous"),
n.Pops = nrow(samples)
)
gdist_inputs <- gdist.prep(
n.Pops = nrow(samples),
response = lower(as.matrix(dist(samples[, 2:3]))),
samples = terra::vect(samples[, 2:3], type = "points"),
method = "commuteDistance",
commute.approx = commute_plan$commute.approx,
approx.factor = commute_plan$approx.factor
)
# Use gdist_inputs during GA optimization, then run the final best surface with
# jl.prep() / Run_CS.jl() if you need Circuitscape-matched output or a current map.Julia uses a built-in package manager (Pkg) that stores package
versions in environment manifests. The key environment for
ResistanceGA2 is the default environment
for the Julia version you’re using.
From the Julia REPL:
This shows all packages installed in the current environment. You
should see Circuitscape listed.
If you have multiple Julia versions installed, make sure
JULIA_HOME points to the one where you installed
Circuitscape.jl. Different Julia versions have separate
environments.
To update Circuitscape to the latest version, run from the Julia REPL:
After updating, test the new version before running a large optimization:
jl_inputs_test <- jl.prep(
n.Pops = nrow(samples),
CS_Point.File = vect(samples[, 2:3], type = "points"),
JULIA_HOME = JULIA_HOME,
run_test = TRUE
)Symptom: Error: “Could not find Julia” or “JULIA_BINDIR not set”
Solutions: 1. Verify JULIA_BINDIR is
set correctly: Sys.getenv("JULIA_BINDIR") 2. Confirm the
path exists and contains the Julia executable:
bindir <- Sys.getenv("JULIA_BINDIR")
dir.exists(bindir)
file.exists(file.path(bindir, "julia.exe")) # Windows
file.exists(file.path(bindir, "julia")) # macOS/LinuxSet the path explicitly before calling any package functions:
Symptom: using Circuitscape fails with
“Package not installed”
Solutions: 1. Open Julia directly (not from R) and
run Pkg.add("Circuitscape"). 2. Confirm you are using the
correct Julia version:
JuliaConnectoR::juliaEval("string(VERSION)"). 3. Check that
Circuitscape was installed in the default environment, not a
project-specific environment:
julia using Pkg Pkg.activate() # activate default environment Pkg.add("Circuitscape")
Symptom: The first call to jl.prep() or
Run_CS.jl() takes 30–60 seconds.
This is normal Julia behavior (JIT compilation on first use). Subsequent calls within the same R session will be much faster because Julia caches compiled code. The startup cost is paid only once per R session.
To minimize the impact on optimization runs, call
jl.prep() once at the beginning of your session (which
triggers Julia startup) before beginning optimization.
Symptom: Circuitscape crashes or returns an error about insufficient memory.
Solutions: 1. Use precision = TRUE in
jl.prep() to switch to single-precision arithmetic. 2. Use
cholmod = FALSE to switch from the direct CHOLMOD solver to
an iterative solver. 3. Aggregate the resistance raster to a coarser
resolution. 4. Reduce the number of sample populations if feasible.
Symptom: Error messages about inability to write to temporary files.
Solutions: 1. Set scratch in
jl.prep() to a directory where you have write
permissions:
tempdir() is writable:
dir.exists(tempdir())Symptom: Julia-related errors appear after the optimization has been running for a while.
This can occur if Julia’s memory or disk use causes the process to be killed. Solutions:
precision = TRUE and/or
cholmod = FALSE to reduce memory use.The Julia backend provides Circuitscape-compatible pairwise
resistance distances and cumulative current maps from within R. Once
Julia and Circuitscape.jl are installed and JULIA_BINDIR is
set, the interface is nearly identical to the gdistance
workflow:
jl.prep() -> jl_inputs
Run_CS.jl() -> pairwise distances or current map
SS_optim() -> pass jl.inputs instead of gdist.inputs
MS_optim() -> pass jl.inputs instead of gdist.inputs
all_comb() -> pass jl.inputs instead of gdist.inputs
For iterative optimization on large landscapes or large sample sets,
consider using gdistance::commuteDistance() for the
optimization step and running Circuitscape once with the optimal
parameters for final output.
library(ResistanceGA2)
library(terra)
# 1. Point ResistanceGA2 at the Julia bin/ directory for this project.
JULIA_HOME <- Sys.getenv("JULIA_BINDIR")
stopifnot(nzchar(JULIA_HOME), dir.exists(JULIA_HOME))
# 2. Prepare the sample points and response vector.
pts <- terra::vect(samples[, 2:3], type = "points")
pairwise_genetic <- lower(as.matrix(dist(samples[, 2:3])))
# 3. Build Circuitscape inputs. Use run_test = TRUE the first time on a machine.
jl_inputs <- jl.prep(
n.Pops = nrow(samples),
response = pairwise_genetic,
CS_Point.File = pts,
JULIA_HOME = JULIA_HOME,
run_test = TRUE,
silent = TRUE
)
# 4. Calculate pairwise Circuitscape distances for one surface.
surface <- terra::subset(terra::unwrap(resistance_surfaces), "continuous")
cs_vector <- Run_CS.jl(
jl.inputs = jl_inputs,
r = surface,
JULIA_HOME = JULIA_HOME
)
# 5. Use jl.inputs in optimization when Circuitscape output is required.
surfaces <- resistance_surfaces
if (inherits(surfaces, "PackedSpatRaster")) {
surfaces <- terra::unwrap(surfaces)
}
ga_inputs <- GA.prep(
raster = surfaces,
Results.dir = file.path(tempdir(), "rga2-julia-results")
)
ss_out <- SS_optim(
GA.inputs = ga_inputs,
jl.inputs = jl_inputs
)How to read the output: jl.prep()
returns a configuration list that stores the sample locations, MLPE data
frame, solver settings, and Julia path used by the Circuitscape engine.
Run_CS.jl() returns the pairwise resistance distances that
can be modeled like any other resistance-distance vector.
SS_optim() and MS_optim() use the same
optimization interface as the gdistance workflow, but each
candidate surface is evaluated by Circuitscape through Julia.
| Function | Purpose |
|---|---|
jl.prep() |
Prepare sample points, response data, solver settings, and Julia configuration for Circuitscape-based optimization |
Run_CS.jl() |
Run the package’s optimization-oriented Circuitscape wrapper and return pairwise distances, a matrix, or a current map |
Run_CS.ini() |
Run Circuitscape with direct .ini-style options for
advanced scenarios and debugging |
GA.prep() |
Prepare the raster stack, transformation bounds, and GA control settings used by optimization functions |
SS_optim() |
Optimize one resistance surface at a time using
jl.inputs or gdist.inputs |
MS_optim() |
Optimize a composite resistance surface from multiple layers using
jl.inputs or gdist.inputs |
vignette("GettingStarted", package = "ResistanceGA2"),
the core package workflow and bundled data.vignette("ResistanceGA", package = "ResistanceGA2"),
full single-surface, multisurface, all-combinations, and bootstrap
optimization examples.vignette("Circuitscape_INI", package = "ResistanceGA2"),
direct .ini-style Circuitscape control from R.vignette("Surface_Transformations", package = "ResistanceGA2"),
transformation families and how the GA searches them.McRae, B. H. 2006. Isolation by resistance. Evolution 60:1551-1561. https://doi.org/10.1111/j.0014-3820.2006.tb00500.x
McRae, B. H., B. G. Dickson, T. H. Keitt, and V. B. Shah. 2008. Using circuit theory to model connectivity in ecology, evolution, and conservation. Ecology 89:2712–2724.
Peterman, W. E. 2018. ResistanceGA: An R package for the optimization of resistance surfaces using genetic algorithms. Methods in Ecology and Evolution 9:1638-1647. https://doi.org/10.1111/2041-210X.12984