--- title: "Getting Started with circuitscaper" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting Started with circuitscaper} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` ## What is circuit-theory connectivity? Landscape connectivity — how easily organisms, genes, or ecological processes can move across a landscape — is central to spatial ecology and conservation biology. Circuit theory offers a powerful way to model it. The idea is to treat a landscape as an electrical circuit: each raster cell is a node, adjacent cells are connected by resistors, and current flowing through the network reveals movement patterns. Unlike least-cost path methods, which find only the single cheapest route, circuit theory considers all possible pathways simultaneously. This makes it especially effective at identifying movement corridors and pinch points — places where connectivity is funneled through narrow gaps. circuitscaper brings this approach to R by wrapping two Julia tools: **Circuitscape** (which computes connectivity between specific sites) and **Omniscape** (which maps connectivity continuously across an entire landscape). This vignette provides a basic overview of the circuitscaper R package. For more details on circuit theory and the underlying Julia algorithms, see the [Circuitscape](https://circuitscape.org) and [Omniscape](https://docs.circuitscape.org/Omniscape.jl/latest/) documentation. ## Installation circuitscaper requires Julia (>= 1.9) with the Circuitscape and Omniscape packages. You can install everything from R: ```{r install, eval = FALSE} # Install circuitscaper (from GitHub during development) # remotes::install_github("matthewkling/circuitscaper") library(circuitscaper) # First time only: install Julia and required packages cs_install_julia() ``` If you already have Julia installed, `cs_setup()` will find it automatically or you can point to it: ```{r setup, eval = FALSE} cs_setup(julia_home = "/path/to/julia/bin") ``` ## Key Inputs ### The resistance surface Every analysis starts with a **resistance surface** — a raster where each cell's value represents how difficult it is for the organism or process of interest to move through that location. These are typically derived from land cover or habitat suitability models. For example, a resistance surface for a forest-dwelling mammal might assign low values to forest (easy to traverse), moderate values to grassland, and high values to urban areas or water. (Note that while the Circuitscape.jl Julia library allows resistance datasets to be specified as adjacency graph networks, circuitscaper currently only supports rasters.) Resistance values must be positive. If your data represents conductance (ease of movement) rather than resistance, set `resistance_is = "conductances"`. ### Focal nodes Circuitscape's focal-node modes (pairwise, one-to-all, all-to-one) require **focal nodes** — the sites between which you want to measure connectivity. These are typically habitat patches, populations, protected areas, or sampling locations. You can provide them as: - A matrix or data.frame of x/y coordinates (simplest) - A raster with positive integer IDs at each site (0 and NA are ignored) ## Choosing a Mode circuitscaper provides four Circuitscape modes and one Omniscape mode. Here's how to choose: **Pairwise** is a common starting point. Use it when you have discrete sites (habitat patches, populations) and want to know how well-connected they are to each other. The resistance matrix is useful as a distance metric for analyses such as isolation by resistance in population genetics. **One-to-all** iterates over each focal node, injecting current at that node and grounding all others. This emphasizes how current disperses outward from each source, making it useful for modeling recolonization potential or identifying which sites have the best overall connectivity to the network. **All-to-one** is the reverse: all other nodes inject current while each node takes a turn as the ground. This emphasizes convergence and is useful for identifying the most accessible or reachable sites — for example, which protected areas are easiest to reach via migration. **Advanced** is for when you want full control over source and ground placement rather than using focal nodes. You provide explicit source current and ground conductance rasters, and a single circuit is solved. This is useful for modeling directional movement between a defined source area and a destination (e.g., current flow between a migratory species' winter and summer ranges). **Omniscape** is fundamentally different — it doesn't require focal nodes at all. It uses a moving window to compute omnidirectional connectivity everywhere in the landscape. Use it for wall-to-wall connectivity mapping when you want to identify corridors and barriers across the full extent. ## Circuitscape: Pairwise Mode ```{r pairwise} library(terra) library(circuitscaper) # Resistance surface (higher values = harder to traverse) resistance <- rast(system.file("extdata/resistance.tif", package = "circuitscaper")) # Option 1: Focal nodes as coordinates (simplest) coords <- matrix(c(10, 40, 40, 40, 10, 10, 40, 10), ncol = 2, byrow = TRUE) result <- cs_pairwise(resistance, coords) # Option 2: Focal nodes as a raster (integer IDs; 0 and NA are not nodes) # locations <- rast("path/to/focal_nodes.tif") # result <- cs_pairwise(resistance, locations) # Cumulative current map -- high values indicate important movement corridors plot(result$current_map) # Pairwise resistance matrix -- can be used as a connectivity distance metric result$resistance_matrix ``` ## Circuitscape: One-to-All and All-to-One Modes ```{r one-to-all} result <- cs_one_to_all(resistance, coords) plot(result) ``` ```{r all-to-one} result <- cs_all_to_one(resistance, coords) plot(result$cumulative_current) ``` ## Circuitscape: Advanced Mode ```{r advanced} source_layer <- rast(system.file("extdata/source.tif", package = "circuitscaper")) ground_layer <- rast(system.file("extdata/ground.tif", package = "circuitscaper")) result <- cs_advanced(resistance, source_layer, ground_layer, ground_is = "conductances") # Current density -- corridors and pinch points where flow is concentrated # i.e. possible preservation priorities plot(result[["current"]]) # Voltage -- analogous to movement probability, decreasing with distance # and resistance from sources plot(result[["voltage"]]) # Power dissipation -- areas of current flow through high-resistance areas, # i.e. possible restoration priorities plot(result[["current"]]^2 * resistance) ``` ## Additional Circuitscape Options The `cs_*` functions expose several additional Circuitscape features. ### Per-pair current and voltage maps By default, pairwise, one-to-all, and all-to-one modes return only the cumulative current map. Set `cumulative_only = FALSE` to include per-pair (or per-node) current layers, and `write_voltage = TRUE` to include voltage layers: ```{r per-pair} result <- cs_pairwise(resistance, coords, cumulative_only = FALSE, write_voltage = TRUE) names(result$current_map) ``` ### Variable source strengths By default, each focal node injects 1 amp of current. Use `source_strengths` to assign different injection strengths per node — useful when sites differ in population size or habitat area: ```{r source-strengths, eval = FALSE} # Strengths in the same order as the locations strengths <- c(2.5, 1.0, 0.5) result <- cs_one_to_all(resistance, coords, source_strengths = strengths) ``` ### Short-circuit regions Short-circuit regions represent areas of zero resistance (e.g., lakes that can be crossed freely, or developed areas that funnel movement). Pass a raster where cells sharing the same positive integer value are short-circuited: ```{r short-circuit, eval = FALSE} polygons <- rast("path/to/short_circuit_regions.tif") result <- cs_pairwise(resistance, locations, short_circuit = polygons) ``` ### Include/exclude pairs For large numbers of focal nodes, you may want to restrict analysis to a subset of pairs: ```{r included-pairs, eval = FALSE} result <- cs_pairwise(resistance, locations, included_pairs = "path/to/pairs.txt") ``` ## Omniscape: Moving-Window Connectivity Omniscape computes omnidirectional connectivity using a moving window. It does not require focal nodes — every cell can serve as a source. The two key parameters are: - **`radius`**: The moving window radius in pixels. This defines the neighborhood over which connectivity is evaluated — conceptually, how far the organism can disperse. Larger radii capture longer-distance connectivity but increase computation time substantially. - **`block_size`**: Aggregation factor for source points (default 1, no aggregation). Setting `block_size = 3` groups sources into 3x3 blocks, reducing the number of circuit solves with typically negligible effects on results. This is the primary knob for trading off speed vs. precision. ```{r omniscape} result <- os_run(resistance, radius = 20, block_size = 3) ``` Omniscape returns up to three layers: - **`cumulative_current`**: Raw current flow summed across all moving-window iterations. Higher values indicate cells that carry more current overall. - **`flow_potential`**: What current flow would look like if resistance were uniform everywhere. This isolates the effect of source geometry from landscape resistance. - **`normalized_current`**: Cumulative current divided by flow potential. Values greater than 1 indicate corridors where connectivity is higher than expected given the arrangement of sources; values less than 1 indicate relative barriers. This is usually the most informative layer. ```{r omniscape-plot} plot(result) ``` ### With source strength weights By default, source strength is derived from the inverse of resistance. You can provide an explicit source strength raster (e.g., habitat quality) to weight sources independently of resistance: ```{r omniscape-source, eval = FALSE} source_strength <- rast("path/to/habitat_quality.tif") result <- os_run(resistance, radius = 20, source_strength = source_strength) ``` ### Parallel processing For large landscapes, enable Julia multithreading to reduce compute times. Julia's thread count is fixed at startup, so set it via `cs_setup()` before any other circuitscaper calls: ```{r omniscape-parallel, eval = FALSE} # Set thread count at the start of your session cs_setup(threads = 4) result <- os_run(resistance, radius = 20, block_size = 5, parallelize = TRUE) ``` ## Performance Circuitscape computation time scales with the number of non-NA cells in the resistance raster and, for focal-node modes, with the number of pairs or iterations. A few practical guidelines: - **Raster size**: Problems up to ~1 million cells run comfortably. For larger landscapes, consider aggregating the resistance surface before analysis. - **Number of focal nodes**: Pairwise mode solves n*(n-1)/2 circuits, so computation grows quadratically with the number of nodes. Use `included_pairs` to limit the set if needed. - **Omniscape**: Computation is dominated by the number of source pixels and the `radius`. Use `block_size` to reduce the source count and keep `radius` as small as ecologically justified. Enable `parallelize = TRUE` for large runs. - **Solver choice**: The default `"cg+amg"` (iterative) solver works well for large problems. For small rasters (under ~10,000 cells), `"cholmod"` (direct solver) may be faster. ## Saving Outputs By default, intermediate files are written to a temporary directory and cleaned up. To persist output files (ASC rasters, INI configuration, and resistance matrices), use `output_dir`: ```{r output-dir, eval = FALSE} result <- cs_pairwise(resistance, locations, output_dir = "my_output_directory") ``` ## Tips - **CRS preservation**: circuitscaper captures the CRS from your input raster and reattaches it to outputs, even though Circuitscape's ASCII grid format doesn't carry CRS information. - **Conductance input**: If your surface represents ease of movement rather than difficulty, set `resistance_is = "conductances"` to avoid inverting values manually. There's a similar option for grounds in advanced mode. - **Normalized current for Circuitscape**: Omniscape returns normalized current (actual current / flow potential) automatically. You can compute the equivalent for Circuitscape by running the analysis twice — once with your resistance surface and once with a uniform surface — and dividing: ```r result <- cs_pairwise(resistance, sites) result_null <- cs_pairwise(resistance * 0 + 1, sites) normalized <- result$current_map / result_null$current_map ``` Values greater than 1 indicate areas where landscape resistance is concentrating current flow; values less than 1 indicate areas where current is lower than expected from geometry alone. - **Verbose output**: Set `verbose = TRUE` to see Circuitscape's solver progress, useful for debugging or monitoring long runs.