Skip to content

Circuit connectivity

Connectivity from electrical circuit theory: omnidirectional current with moving windows (Omniscape), current and effective resistance between focal points, sources and grounds, on rasters and on networks (Circuitscape). Tutorial: circuit connectivity.

ls.omniscape is at the top level; the other functions are in the ls.circuit module (from landschaft import circuit).

Resistance, conductance and current

A resistance map says how hard each cell is to cross (1 = easy, larger = harder); conductance is its inverse. Current is injected at sources and leaves at grounds; the current through a cell shows how much movement funnels through it. The effective resistance between two points falls when there are more, shorter or easier paths between them.

Omnidirectional current

omniscape

omniscape(
    resistance,
    radius,
    *,
    source=None,
    block_size=1,
    buffer=0,
    source_threshold: float = 0.0,
    r_cutoff: float = inf,
    resistance_is_conductance: bool = False,
    four_neighbors: bool = False,
    flow_potential: bool = False,
    normalized_current: bool = False,
    correct_artifacts: bool = True,
    mask_nodata: bool = True,
    reclass_table=None,
    condition1=None,
    condition2=None,
    condition1_future=None,
    condition2_future=None,
    compare_to_future: str = "none",
    comparison1: str = "within",
    comparison2: str = "within",
    condition1_lower: float = 0.0,
    condition1_upper: float = 0.0,
    condition2_lower: float = 0.0,
    condition2_upper: float = 0.0,
    precision: str = "double",
    memory_limit: float | None = None,
    n_threads: int = 0,
)

Omnidirectional connectivity with Omniscape's moving-window circuits.

Omniscape 0.7 (run_omniscape; McRae et al. 2016, Landau et al. 2021): every block_size × block_size block of cells whose source strengths add up to more than 0 is a target. Its window is the circle of radius + buffer cells around the block's centre; the block's strength is injected into the sources within radius (the target block left out, shared in proportion to their strengths), the target cell is grounded, and the current through every cell of the window is added to the cumulative map. Windows are solved exactly (Circuitscape's circuit, see advanced()) in parallel and summed in a fixed order, so the result does not depend on the number of threads.

Omniscape's details are kept: windows and circles cut at the map edges, the target block zeroed at radius + buffer cells from the window's corner (so not at the target when the window is cut on the left or the top), and, with block_size > 1, its block-artifact correction.

Parameters:

Name Type Description Default
resistance

Resistance raster (path, DataArray or array); NaN or the nodata value is NoData (infinite resistance). Values must be > 0.

required
radius

Radius of the moving window, in cells.

required
source

Source strengths (NaN is 0; values below source_threshold are set to 0, as when Omniscape reads a source file). Default: from the resistance, as source_from_resistance() with r_cutoff.

None
block_size

Side of the target blocks (made odd as Omniscape does: an even size gets + 1, with a warning).

1
buffer

Cells added to the window beyond radius (conductance only).

0
source_threshold float

Omniscape's options of the same names.

0.0
r_cutoff float

Omniscape's options of the same names.

0.0
resistance_is_conductance bool

The raster holds conductances.

False
four_neighbors bool

connect_four_neighbors_only.

False
flow_potential bool

Also compute the flow potential (the same circuits with conductance 1) and the current normalised by it (0 where both are 0).

False
normalized_current bool

Also compute the flow potential (the same circuits with conductance 1) and the current normalised by it (0 where both are 0).

False
correct_artifacts bool

Omniscape's correction of block artifacts (only with block_size > 1).

True
mask_nodata bool

NaN on the NoData cells of the resistance.

True
reclass_table

Reclassify the resistance first (reclassify_resistance): a two-column table (value, new value; NaN makes NoData), a dict, or the path of an Omniscape reclass file. The default sources come from the reclassified resistance.

None
condition1

Conditional connectivity (conditional, n_conditions): after a window's sources are scaled, those whose condition1 (and condition2) does not match the target block's value of the future condition are set to 0. The future conditions default to the present ones; compare_to_future ("none", "1", "2" or "both") says which futures are used, as in Omniscape.

None
condition2

Conditional connectivity (conditional, n_conditions): after a window's sources are scaled, those whose condition1 (and condition2) does not match the target block's value of the future condition are set to 0. The future conditions default to the present ones; compare_to_future ("none", "1", "2" or "both") says which futures are used, as in Omniscape.

None
condition1_future

Conditional connectivity (conditional, n_conditions): after a window's sources are scaled, those whose condition1 (and condition2) does not match the target block's value of the future condition are set to 0. The future conditions default to the present ones; compare_to_future ("none", "1", "2" or "both") says which futures are used, as in Omniscape.

None
condition2_future

Conditional connectivity (conditional, n_conditions): after a window's sources are scaled, those whose condition1 (and condition2) does not match the target block's value of the future condition are set to 0. The future conditions default to the present ones; compare_to_future ("none", "1", "2" or "both") says which futures are used, as in Omniscape.

None
comparison1 str

"within": kept when lower <= condition - median(block) <= upper; "equal": kept when condition == mode(block).

'within'
comparison2 str

"within": kept when lower <= condition - median(block) <= upper; "equal": kept when condition == mode(block).

'within'
condition1_lower str

"within": kept when lower <= condition - median(block) <= upper; "equal": kept when condition == mode(block).

'within'
condition1_upper str

"within": kept when lower <= condition - median(block) <= upper; "equal": kept when condition == mode(block).

'within'
condition2_lower str

"within": kept when lower <= condition - median(block) <= upper; "equal": kept when condition == mode(block).

'within'
condition2_upper str

"within": kept when lower <= condition - median(block) <= upper; "equal": kept when condition == mode(block).

'within'
precision str

"single" reads the rasters as float32 and returns float32 maps, as Omniscape's precision = single; the arithmetic stays double.

'double'
memory_limit float | None

Bytes the solves may use (default: half of the physical memory); with large windows fewer threads run when each thread's factorisation of a full window would not fit (attrs["threads"]).

None
n_threads int

Threads (0 or None: all cores but one).

0

Returns:

Type Description
Dataset

cum_currmap and, when asked for, flow_potential and normalized_cum_currmap, on the resistance's grid; attrs records the options, the number of targets and of threads used.

See Also

landschaft.circuit.omniscape_ini : run an Omniscape INI file. landschaft.circuit.circuitscape_pairwise : current between focal points.

Examples:

A 30 x 30 map with a hard barrier (resistance 20) that has a gap in the middle:

>>> import numpy as np
>>> import landschaft as ls
>>> resistance = np.ones((30, 30))
>>> resistance[:, 14:16] = 20
>>> resistance[10:20, 14:16] = 1                 # the gap
>>> flow = ls.omniscape(resistance, radius=6, flow_potential=True, normalized_current=True)
>>> list(flow.data_vars)
['cum_currmap', 'flow_potential', 'normalized_cum_currmap']

Current concentrates in the gap:

>>> current = flow["cum_currmap"]
>>> round(float(current[15, 14]), 2), round(float(current[2, 14]), 2)
(5.16, 1.55)

omniscape_ini

omniscape_ini(path, *, write_outputs: bool = True, n_threads: int = 0)

Run an Omniscape INI file, as run_omniscape(path) does.

Every option of Omniscape 0.7 is read with its defaults (radius, resistance_file and project_name are required); file paths are taken from the working directory, or else next to the INI file. With write_outputs the outputs go to project_name (_1, _2… when it exists) as Omniscape writes them: config.ini, cum_currmap, flow_potential, normalized_cum_currmap and classified_resistance, GeoTIFF (write_as_tif) or ASCII grids, NoData -9999. solver, parallelize, parallel_batch_size and suppress_cs_messages have no effect: windows are always solved exactly, with n_threads threads (0: all cores but one).

Parameters:

Name Type Description Default
path

The INI file.

required
write_outputs bool

Write the outputs to the project folder.

True
n_threads int

Threads (0 or None: all cores but one).

0

Returns:

Type Description
Dataset

As omniscape(), plus classified_resistance when the resistance was reclassified.

Examples:

>>> import numpy as np
>>> import xarray as xr
>>> import landschaft as ls
>>> from landschaft import circuit
>>> from rasterio.transform import from_origin
>>> resistance = xr.DataArray(np.ones((10, 10)), dims=("y", "x")).rio.write_transform(
...     from_origin(0, 10, 1, 1)).rio.write_crs("EPSG:3035")
>>> _ = ls.write_raster(resistance, "resistance.tif")
>>> _ = open("omniscape.ini", "w").write('''[Required]
... resistance_file = resistance.tif
... radius = 3
... project_name = corridors
... source_from_resistance = true
... ''')
>>> flow = circuit.omniscape_ini("omniscape.ini")
>>> import os
>>> sorted(os.listdir("corridors"))
['config.ini', 'cum_currmap.tif']

source_from_resistance

source_from_resistance(
    resistance,
    *,
    r_cutoff: float = inf,
    resistance_is_conductance: bool = False,
    reclass_table=None,
)

Source strengths from the resistance, as Omniscape's source_from_resistance.

The conductance (1 / resistance, or the values themselves with resistance_is_conductance) after the optional reclassification, set to 0 where it is below 1 / r_cutoff and on NoData.

Parameters:

Name Type Description Default
resistance

Resistance (or conductance) raster.

required
r_cutoff float

Resistances above this give no source.

inf
resistance_is_conductance bool

The raster holds conductances.

False
reclass_table

Reclassification applied first (see omniscape()).

None

Returns:

Type Description
ndarray or DataArray

Source strengths.

Examples:

>>> import numpy as np
>>> from landschaft import circuit
>>> circuit.source_from_resistance(np.array([[1.0, 2.0], [4.0, np.nan]]))
array([[1.  , 0.5 ],
       [0.25, 0.  ]])

read_reclass_table

read_reclass_table(path) -> ndarray

Read an Omniscape reclass table.

Two whitespace-separated columns (value, new value); missing in the new column makes NoData.

Parameters:

Name Type Description Default
path

The text file.

required

Returns:

Type Description
ndarray

(k, 2) array (NaN for missing).

Examples:

>>> from landschaft import circuit
>>> _ = open("reclass.txt", "w").write('''1 10
... 2 50
... 3 missing
... ''')
>>> circuit.read_reclass_table("reclass.txt")
array([[ 1., 10.],
       [ 2., 50.],
       [ 3., nan]])

reclassify

reclassify(resistance: ndarray, table) -> ndarray

Reclassify a resistance array, as Omniscape's reclassify_resistance!.

Every row (value, new value) is applied in turn, matched against the original values; NaN in the new column makes NoData. Values not in the table are kept.

Parameters:

Name Type Description Default
resistance ndarray

The array.

required
table

(k, 2) table, e.g. from read_reclass_table().

required

Returns:

Type Description
ndarray

Examples:

>>> import numpy as np
>>> from landschaft import circuit
>>> table = np.array([[1, 10], [2, 50], [3, np.nan]])
>>> circuit.reclassify(np.array([[1.0, 2.0], [3.0, 4.0]]), table)
array([[10., 50.],
       [nan,  4.]])

parse_ini

parse_ini(path) -> dict[str, str]

Read an INI file as Omniscape's parse_cfg does.

Lines starting with [ are skipped; every other line is split at its first = (key right-stripped, value stripped).

Parameters:

Name Type Description Default
path

The INI file.

required

Returns:

Type Description
dict of str to str

Examples:

>>> from landschaft import circuit
>>> _ = open("run.ini", "w").write('''[Required]
... radius = 3
... project_name = corridors
... ''')
>>> circuit.parse_ini("run.ini")
{'radius': '3', 'project_name': 'corridors'}

Focal points on rasters

circuitscape_pairwise

circuitscape_pairwise(
    habitat,
    points,
    *,
    habitat_is_resistance: bool = True,
    polygons=None,
    mask=None,
    four_neighbors: bool = False,
    avg_resistances: bool = False,
    included_pairs=None,
    voltage_maps: bool = False,
    current_maps: bool = False,
    cum_map: bool = False,
    max_map: bool = False,
    set_focal_node_currents_to_zero: bool = False,
    set_null_currents_to_nodata: bool = False,
    set_null_voltages_to_nodata: bool = False,
    log_transform_maps: bool = False,
    reclass: dict | None = None,
    n_threads: int = 0,
) -> CircuitscapeResult

Circuitscape's pairwise mode on rasters (scenario = pairwise).

Effective resistances between every pair of focal points (points: a raster of ids, cells of one id forming a focal region, or a FocalPoints), on the graph of circuitscape_advanced(). Every component is factorised once and its pairs solved in batches (without maps or exclusions, Circuitscape's shortcut: only the pairs of the first focal point are solved). included_pairs (IncludedPairs or a path) restricts the pairs as Circuitscape does. current_maps and voltage_maps keep every pair's maps in result.maps (keyed by the two ids); cum_map / max_map sum / take the maximum of the pairs' current maps.

Parameters:

Name Type Description Default
habitat

Resistance (or conductance) raster: path, DataArray or array.

required
points

Raster of focal-point ids (0 / NoData elsewhere; the cells of one id form a focal region) or a FocalPoints.

required
habitat_is_resistance bool

habitat holds resistances (default) or conductances.

True
polygons

Short-circuit regions: a raster of ids (> 0); the habitat cells of one region are merged into one node.

None
mask

Raster; cells where it is NoData or not positive are removed.

None
four_neighbors bool

Connect the four orthogonal neighbours only.

False
avg_resistances bool

Join neighbours by the average of their resistances instead of their conductances.

False
included_pairs

IncludedPairs (or the path of a Circuitscape include/exclude file) restricting the pairs.

None
voltage_maps bool

Keep the voltage / current map of every pair (or point) in result.maps.

False
current_maps bool

Keep the voltage / current map of every pair (or point) in result.maps.

False
cum_map bool

Sum / maximum of the current maps (result.cum_current, result.max_current).

False
max_map bool

Sum / maximum of the current maps (result.cum_current, result.max_current).

False
set_focal_node_currents_to_zero bool

Current 0 on the focal nodes in the maps.

False
set_null_currents_to_nodata bool

NoData instead of 0 where the current / voltage is 0 in the maps.

False
set_null_voltages_to_nodata bool

NoData instead of 0 where the current / voltage is 0 in the maps.

False
log_transform_maps bool

log10 of the current maps.

False
reclass dict | None

{value: new value} applied to habitat first.

None
n_threads int

Threads (0 or None: all cores but one).

0

Returns:

Type Description
CircuitscapeResult

resistances (ids in the first row and column; -1: not connected), cum_current, max_current, maps.

See Also

omniscape : omnidirectional current without focal points.

Examples:

Three focal points on a map with a harder column in the middle:

>>> import numpy as np
>>> from landschaft import circuit
>>> resistance = np.ones((10, 10))
>>> resistance[:, 5] = 10                       # a harder column in the middle
>>> points = np.zeros((10, 10), dtype=int)
>>> points[1, 1], points[8, 1], points[5, 8] = 1, 2, 3
>>> result = circuit.circuitscape_pairwise(resistance, points, cum_map=True)
>>> result.resistances.round(4)
array([[0.    , 1.    , 2.    , 3.    ],
       [1.    , 0.    , 0.8515, 0.8567],
       [2.    , 0.8515, 0.    , 0.8409],
       [3.    , 0.8567, 0.8409, 0.    ]])
>>> result.cum_current.shape
(10, 10)

circuitscape_one_to_all

circuitscape_one_to_all(
    habitat,
    points,
    *,
    all_to_one: bool = False,
    habitat_is_resistance: bool = True,
    polygons=None,
    mask=None,
    four_neighbors: bool = False,
    avg_resistances: bool = False,
    included_pairs=None,
    strengths=None,
    voltage_maps: bool = False,
    current_maps: bool = False,
    cum_map: bool = False,
    max_map: bool = False,
    set_focal_node_currents_to_zero: bool = False,
    reclass: dict | None = None,
    n_threads: int = 0,
) -> CircuitscapeResult

Circuitscape's one-to-all (or, with all_to_one, all-to-one) mode on rasters.

For every focal point in turn (points as in circuitscape_pairwise(); the focal points and regions are merged into the short-circuit regions): one-to-all injects 1 A (or its strengths value: a two-column table of id and strength) there with every other focal point a direct ground; all-to-one grounds it and injects 1 A (or the strengths) at the others. Only the point's component is solved; the points run in parallel. Circuitscape's quirks are kept (points pruned by included_pairs in both of its modes, excluded points still sourcing in all-to-one, strengths indexed by position).

Parameters:

Name Type Description Default
habitat

Resistance (or conductance) raster: path, DataArray or array.

required
points

Raster of focal-point ids or a FocalPoints.

required
all_to_one bool

All-to-one instead of one-to-all.

False
habitat_is_resistance bool

habitat holds resistances (default) or conductances.

True
polygons

Short-circuit regions: a raster of ids (> 0); the habitat cells of one region are merged into one node.

None
mask

Raster; cells where it is NoData or not positive are removed.

None
four_neighbors bool

Connect the four orthogonal neighbours only.

False
avg_resistances bool

Join neighbours by the average of their resistances instead of their conductances.

False
strengths

Two-column table (id, strength) of the current of every point.

None
included_pairs

IncludedPairs (or the path of a Circuitscape include/exclude file) restricting the pairs.

None
voltage_maps bool

Keep the voltage / current map of every pair (or point) in result.maps.

False
current_maps bool

Keep the voltage / current map of every pair (or point) in result.maps.

False
cum_map bool

Sum / maximum of the current maps (result.cum_current, result.max_current).

False
max_map bool

Sum / maximum of the current maps (result.cum_current, result.max_current).

False
set_focal_node_currents_to_zero bool

Current 0 on the focal nodes in the maps.

False
reclass dict | None

{value: new value} applied to habitat first.

None
n_threads int

Threads (0 or None: all cores but one).

0

Returns:

Type Description
CircuitscapeResult

resistances: ids and the voltage over the strength at each point (one-to-all; -1 where nothing was solved) or 0 (all-to-one); cum_current, max_current, and the per-point maps in maps.

Examples:

>>> import numpy as np
>>> from landschaft import circuit
>>> resistance = np.ones((10, 10))
>>> resistance[:, 5] = 10                       # a harder column in the middle
>>> points = np.zeros((10, 10), dtype=int)
>>> points[1, 1], points[8, 1], points[5, 8] = 1, 2, 3
>>> result = circuit.circuitscape_one_to_all(resistance, points, cum_map=True)
>>> result.resistances.round(4)
array([[1.    , 0.6438],
       [2.    , 0.632 ],
       [3.    , 0.6359]])

circuitscape_ini

circuitscape_ini(path, *, write_outputs: bool = True, n_threads: int = 0)

Run a Circuitscape INI file, as Circuitscape.compute(path) does.

Raster data give a CircuitscapeResult; networks (data_type = network, pairwise or advanced) a NetworkResult (see circuitscape_network_pairwise()).

Paths are read from the working directory, or else next to the INI file. With write_outputs the maps go next to output_file as Circuitscape names them (<base>_voltmap, <base>_curmap...; ASCII grids, or GeoTIFF with write_as_tif) and output_file holds the configuration. solver, precision and parallelize have no effect: every solve is exact, in double precision, with n_threads threads.

Parameters:

Name Type Description Default
path

The INI file (Circuitscape 5 / 6 options).

required
write_outputs bool

Write the output files, as Circuitscape does.

True
n_threads int

Threads (0 or None: all cores but one).

0

Returns:

Type Description
CircuitscapeResult or NetworkResult

Examples:

A pairwise run from GeoTIFF inputs:

>>> import numpy as np
>>> from landschaft import circuit
>>> resistance = np.ones((10, 10))
>>> resistance[:, 5] = 10                       # a harder column in the middle
>>> points = np.zeros((10, 10), dtype=int)
>>> points[1, 1], points[8, 1], points[5, 8] = 1, 2, 3
>>> import xarray as xr
>>> import landschaft as ls
>>> from rasterio.transform import from_origin
>>> def as_raster(values):
...     grid = xr.DataArray(values.astype("float64"), dims=("y", "x"))
...     return grid.rio.write_transform(from_origin(0, 10, 1, 1)).rio.write_crs("EPSG:3035")
>>> _ = ls.write_raster(as_raster(resistance), "resistance.tif")
>>> _ = ls.write_raster(as_raster(points), "points.tif")
>>> _ = open("pairwise.ini", "w").write('''[Options]
... data_type = raster
... scenario = pairwise
... habitat_file = resistance.tif
... habitat_map_is_resistances = True
... point_file = points.tif
... output_file = out/pairwise.out
... ''')
>>> result = circuit.circuitscape_ini("pairwise.ini")
>>> round(float(result.resistances[1, 2]), 4)
0.8515
>>> import os
>>> sorted(os.listdir("out"))
['pairwise.out', 'pairwise_resistances.out', 'pairwise_resistances_3columns.out']

CircuitscapeResult dataclass

CircuitscapeResult(
    voltage: ndarray | None = None,
    current: ndarray | None = None,
    nodemap: ndarray | None = None,
    resistances: ndarray | None = None,
    cum_current: ndarray | None = None,
    max_current: ndarray | None = None,
    maps: dict = dict(),
)

Bases: Saveable

Circuitscape's outputs of a raster run.

Attributes:

Name Type Description
voltage, current

Per-cell maps (0 on cells without a node, as Circuitscape writes them in advanced mode).

nodemap ndarray | None

Node number of every cell (-1: no node).

resistances ndarray | None

Pairwise and one-to-all: the matrix Circuitscape returns and writes, ids in the first row and column (-1: not connected).

cum_current, max_current

Sum and maximum of the pairs' (or points') current maps.

maps dict

Per-pair (or per-point) maps, keyed by the ids.

Examples:

>>> import numpy as np
>>> from landschaft import circuit
>>> resistance = np.ones((10, 10))
>>> resistance[:, 5] = 10                       # a harder column in the middle
>>> points = np.zeros((10, 10), dtype=int)
>>> points[1, 1], points[8, 1], points[5, 8] = 1, 2, 3
>>> result = circuit.circuitscape_pairwise(resistance, points, cum_map=True)
>>> result.resistances[0, 1:].tolist()
[1.0, 2.0, 3.0]

FocalPoints dataclass

FocalPoints(rows: ndarray, cols: ndarray, ids: ndarray)

Focal points as Circuitscape reads them: 1-based rows and columns and ids, sorted by id.

Cells sharing an id form a focal region.

Attributes:

Name Type Description
rows, cols

1-based row and column of every cell.

ids ndarray

Focal-point id of every cell.

Examples:

>>> import numpy as np
>>> from landschaft import circuit
>>> points = circuit.FocalPoints(rows=np.array([2, 9, 6]), cols=np.array([2, 2, 9]),
...                              ids=np.array([1, 2, 3]))
>>> points.has_regions
False

cells property

cells: ndarray

0-based (rows, cols) of the cells, for indexing arrays.

has_regions property

has_regions: bool

Whether an id covers several cells (focal regions).

IncludedPairs dataclass

IncludedPairs(mode: str, ids: ndarray, matrix: ndarray)

Circuitscape's include / exclude pairs.

Attributes:

Name Type Description
mode str

"include" (only the marked pairs are solved) or "exclude" (the marked pairs are skipped).

ids ndarray

Focal-point ids.

matrix ndarray

len(ids) x len(ids) 0/1 matrix marking the pairs.

Examples:

Solve only the pairs of point 1:

>>> import numpy as np
>>> from landschaft import circuit
>>> resistance = np.ones((10, 10))
>>> resistance[:, 5] = 10                       # a harder column in the middle
>>> points = np.zeros((10, 10), dtype=int)
>>> points[1, 1], points[8, 1], points[5, 8] = 1, 2, 3
>>> pairs = circuit.IncludedPairs("include", np.array([1, 2, 3]),
...                               np.array([[1, 1, 1], [1, 1, 0], [1, 0, 1]]))
>>> sorted(pairs.excluded())
[(2, 3), (3, 2)]
>>> result = circuit.circuitscape_pairwise(resistance, points, included_pairs=pairs)
>>> float(result.resistances[2, 3])
-1.0

excluded

excluded() -> set[tuple[int, int]]

The pairs of ids that are not solved.

Returns:

Type Description
set of (int, int)

Sources and grounds

circuitscape_advanced

circuitscape_advanced(
    habitat,
    source,
    ground,
    *,
    habitat_is_resistance: bool = True,
    ground_is_resistance: bool = True,
    polygons=None,
    mask=None,
    four_neighbors: bool = False,
    avg_resistances: bool = False,
    remove_src_or_gnd: str = "keepall",
    unit_currents: bool = False,
    direct_grounds: bool = False,
    reclass: dict | None = None,
    n_threads: int = 0,
) -> CircuitscapeResult

Circuitscape's advanced mode on rasters (scenario = advanced).

Every cell of habitat (resistance, or conductance with habitat_is_resistance=False; NaN or -9999 is NoData) with a conductance

0 is a node, joined to its 8 (or 4) neighbours by the average conductance (or, with avg_resistances, the average resistance) of the two cells; the cells of a short-circuit region (polygons: ids > 0) that holds habitat are one node. source gives the current injected per cell, ground the resistance (or conductance) to ground per cell (NoData: no ground; 0 resistance: a direct ground); values of the cells of one node add up. remove_src_or_gnd resolves nodes that are both (Circuitscape's policies, with their quirks: rmvall only drops the source, and finite grounds dropped by rmvgnd stay on the matrix diagonal). Every connected component with a source and a ground is solved exactly.

Parameters:

Name Type Description Default
habitat

Resistance (or conductance) raster: path, DataArray or array.

required
source

Current injected per cell (NoData: none).

required
ground

Resistance (or conductance) to ground per cell (NoData: no ground; 0 resistance: a direct ground).

required
habitat_is_resistance bool

habitat holds resistances (default) or conductances.

True
polygons

Short-circuit regions: a raster of ids (> 0); the habitat cells of one region are merged into one node.

None
mask

Raster; cells where it is NoData or not positive are removed.

None
four_neighbors bool

Connect the four orthogonal neighbours only.

False
avg_resistances bool

Join neighbours by the average of their resistances instead of their conductances.

False
ground_is_resistance bool

ground holds resistances (default) or conductances.

True
remove_src_or_gnd str

Nodes that are both a source and a ground: "keepall", "rmvsrc", "rmvgnd" or "rmvall".

'keepall'
unit_currents bool

Every source injects 1 A.

False
direct_grounds bool

Every ground is a direct ground (0 V).

False
reclass dict | None

{value: new value} applied to habitat first (Circuitscape's reclass table).

None
n_threads int

Threads (0 or None: all cores but one).

0

Returns:

Type Description
CircuitscapeResult

voltage and current maps (0 off the nodes and in components not solved), nodemap.

Examples:

>>> import numpy as np
>>> from landschaft import circuit
>>> resistance = np.ones((3, 5))
>>> source = np.full((3, 5), np.nan)
>>> source[1, 0] = 1.0                          # 1 A on the left
>>> ground = np.full((3, 5), np.nan)
>>> ground[1, 4] = 0.0                          # a direct ground on the right
>>> result = circuit.circuitscape_advanced(resistance, source, ground)
>>> result.voltage[1].round(3)
array([0.887, 0.615, 0.444, 0.272, 0.   ])

advanced

advanced(
    conductance, source, ground, *, four_neighbors: bool = False, n_threads: int = 0
) -> AdvancedResult

Currents and voltages of a raster circuit with sources and grounds.

Circuitscape 6.1's compute_omniscape_current (advanced mode, the solve behind every Omniscape moving window): each cell with a conductance > 0 is a node joined to its 8 neighbours (4 with four_neighbors) by the average conductance of the two cells, over √2 on the diagonals. source injects current (amps) into its cells; ground connects cells to ground with the given conductance (inf: a direct ground at 0 V). A cell that is both loses its source. Every connected component with a source and a ground is solved exactly (sparse Cholesky in a nested-dissection order; the components in parallel), and a node's current is the larger of its inflow and outflow, leaving out branch currents under 1e-8 of the largest one, as Circuitscape does.

Parameters:

Name Type Description Default
conductance

Rasters of the same shape (paths, DataArrays or arrays). NaN (or the nodata value) in conductance means no node, in source and ground nothing.

required
source

Rasters of the same shape (paths, DataArrays or arrays). NaN (or the nodata value) in conductance means no node, in source and ground nothing.

required
ground

Rasters of the same shape (paths, DataArrays or arrays). NaN (or the nodata value) in conductance means no node, in source and ground nothing.

required
four_neighbors bool

Connect the four orthogonal neighbours only.

False
n_threads int

Threads (0 or None: all cores but one).

0

Returns:

Type Description
AdvancedResult

current and voltage.

See Also

circuitscape_advanced : the same with Circuitscape's options and inputs.

Examples:

One amp injected on the left of a 3 x 5 grid, grounded on the right:

>>> import numpy as np
>>> from landschaft import circuit
>>> conductance = np.ones((3, 5))
>>> source = np.zeros((3, 5))
>>> source[1, 0] = 1.0
>>> ground = np.zeros((3, 5))
>>> ground[1, 4] = np.inf
>>> result = circuit.advanced(conductance, source, ground)
>>> result.voltage.round(3)
array([[0.716, 0.615, 0.444, 0.272, 0.172],
       [0.887, 0.615, 0.444, 0.272, 0.   ],
       [0.716, 0.615, 0.444, 0.272, 0.172]])
>>> result.current.round(3)
array([[0.172, 0.293, 0.293, 0.293, 0.172],
       [1.   , 0.414, 0.414, 0.414, 1.   ],
       [0.172, 0.293, 0.293, 0.293, 0.172]])

AdvancedResult dataclass

AdvancedResult(current: object, voltage: object)

Bases: Saveable

Node currents and voltages of advanced().

DataArrays when the conductance was one, arrays otherwise.

Attributes:

Name Type Description
current object

Current through every cell (max(inflow, outflow) at its node; 0 where nothing was solved).

voltage object

Node voltages (0 in components without a source or a ground, NaN on cells without a node).

Networks

circuitscape_network_pairwise

circuitscape_network_pairwise(
    graph,
    points,
    *,
    graph_is_resistance: bool = True,
    included_pairs=None,
    voltage_maps: bool = False,
    current_maps: bool = False,
    n_threads: int = 0,
) -> NetworkResult

Circuitscape's pairwise mode on a network (data_type = network, scenario = pairwise).

Effective resistances between every pair of focal nodes (points: node numbers, a path or an array; numbered from 0 they are shifted by 1) on the graph of circuitscape_network_advanced(). Every component is factorised once and its pairs solved in batches (Circuitscape's resistance shortcut is for rasters only). included_pairs (IncludedPairs or a path) restricts the pairs. With voltage_maps every pair's node voltages, and with any of the two options every pair's node and branch currents (as Circuitscape writes them) go to result.maps, and the node and branch currents are summed over the pairs.

Parameters:

Name Type Description Default
graph

Edge list: path or (k, 3) array of node, node and resistance (or conductance).

required
points

Focal node numbers: path or array.

required
graph_is_resistance bool

The edge values are resistances (default) or conductances.

True
included_pairs

IncludedPairs or the path of an include/exclude file.

None
voltage_maps bool

Keep every pair's node voltages / node and branch currents in result.maps.

False
current_maps bool

Keep every pair's node voltages / node and branch currents in result.maps.

False
n_threads int

Threads (0 or None: all cores but one).

0

Returns:

Type Description
NetworkResult

resistances (ids in the first row and column), maps, cum_node_currents and cum_branch_currents.

Examples:

>>> import numpy as np
>>> from landschaft import circuit
>>> graph = np.array([[1, 2, 1.0],             # node, node, resistance
...                   [2, 3, 1.0],
...                   [1, 3, 2.0],
...                   [3, 4, 0.5]])
>>> result = circuit.circuitscape_network_pairwise(graph, [1, 3, 4])
>>> result.resistances
array([[0. , 1. , 3. , 4. ],
       [1. , 0. , 1. , 1.5],
       [3. , 1. , 0. , 0.5],
       [4. , 1.5, 0.5, 0. ]])

circuitscape_network_advanced

circuitscape_network_advanced(
    graph,
    sources,
    grounds,
    *,
    graph_is_resistance: bool = True,
    ground_is_resistance: bool = True,
    remove_src_or_gnd: str = "keepall",
    n_threads: int = 0,
) -> NetworkResult

Circuitscape's advanced mode on a network (data_type = network, scenario = advanced).

graph: edge list (node, node, resistance or, with graph_is_resistance=False, conductance), a path or a (k, 3) array; nodes numbered from 1, or from 0 (then every number, the sources' and grounds' too, is shifted by 1 as Circuitscape.jl does). Duplicate edges add up; edges whose conductances add up to 0 are left out. sources and grounds: (node, value) lists (paths or arrays), the current injected and the resistance (or conductance) to ground; a ground resistance of 0 is a direct ground. Every connected component with a source and a ground is solved exactly (remove_src_or_gnd as in circuitscape_advanced()).

Parameters:

Name Type Description Default
graph

Edge list: path or (k, 3) array of node, node and resistance (or conductance).

required
sources

(node, current) rows: path or array.

required
grounds

(node, resistance or conductance to ground) rows; 0 resistance is a direct ground.

required
graph_is_resistance bool

The values are resistances (default) or conductances.

True
ground_is_resistance bool

The values are resistances (default) or conductances.

True
remove_src_or_gnd str

Nodes that are both: "keepall", "rmvsrc", "rmvgnd" or "rmvall".

'keepall'
n_threads int

Threads (0 or None: all cores but one).

0

Returns:

Type Description
NetworkResult

voltages (every node; 0 where not solved), node_currents and branch_currents of the whole graph.

Examples:

One amp into node 1, node 4 grounded:

>>> import numpy as np
>>> from landschaft import circuit
>>> graph = np.array([[1, 2, 1.0],             # node, node, resistance
...                   [2, 3, 1.0],
...                   [1, 3, 2.0],
...                   [3, 4, 0.5]])
>>> result = circuit.circuitscape_network_advanced(graph, [[1, 1.0]], [[4, 0.0]])
>>> result.voltages
array([[1. , 1.5],
       [2. , 1. ],
       [3. , 0.5],
       [4. , 0. ]])
>>> result.branch_currents
array([[1. , 2. , 0.5],
       [1. , 3. , 0.5],
       [2. , 3. , 0.5],
       [3. , 4. , 1. ]])

NetworkResult dataclass

NetworkResult(
    voltages: ndarray | None = None,
    node_currents: ndarray | None = None,
    branch_currents: ndarray | None = None,
    resistances: ndarray | None = None,
    cum_node_currents: ndarray | None = None,
    cum_branch_currents: ndarray | None = None,
    maps: dict = dict(),
    n_components: int = 0,
)

Bases: Saveable

Circuitscape's network outputs, with the node numbers it writes (from 1).

Attributes:

Name Type Description
voltages ndarray | None

Advanced: (n, 2) node, voltage.

node_currents, branch_currents

Advanced: (n, 2) node, current and (k, 3) node, node, current (branch currents above 1e-6, as Circuitscape writes them).

resistances ndarray | None

Pairwise: effective resistances, ids in the first row and column (-1: not connected).

cum_node_currents, cum_branch_currents

Pairwise with maps: node and branch currents summed over the pairs.

maps dict

Per-pair tables keyed ("voltages" | "node_currents" | "branch_currents", id1, id2).

n_components int

Number of connected components of the graph.

Examples:

>>> import numpy as np
>>> from landschaft import circuit
>>> graph = np.array([[1, 2, 1.0],             # node, node, resistance
...                   [2, 3, 1.0],
...                   [1, 3, 2.0],
...                   [3, 4, 0.5]])
>>> result = circuit.circuitscape_network_advanced(graph, [[1, 1.0]], [[4, 0.0]])
>>> result.n_components
1

read_graph

read_graph(edges) -> tuple[ndarray, ndarray, ndarray, bool]

Read a Circuitscape edge list, as its load_graph.

Parameters:

Name Type Description Default
edges

Path of a text file or (k, 3) array: node, node, value.

required

Returns:

Type Description
tuple

(from, to, value, shifted): node numbers (shifted to start at 1 when they start at 0; shifted says so) and edge values.

Examples:

>>> import numpy as np
>>> from landschaft import circuit
>>> graph = np.array([[1, 2, 1.0],             # node, node, resistance
...                   [2, 3, 1.0],
...                   [1, 3, 2.0],
...                   [3, 4, 0.5]])
>>> circuit.read_graph(graph)
(array([1, 2, 1, 3]), array([2, 3, 3, 4]), array([1. , 1. , 2. , 0.5]), False)