- Rebuilt per-cell graphs with flat node arrays and directly constructed compressed sparse row (CSR) edges, retaining integer Dijkstra. Per-thread buffers eliminate per-cell allocations. Together with the caching and BERI improvements below, these changes are about 4x faster for connectedness and PARC and 3x faster for BERI than v2.3 on the bundled example (8 threads); speedups against v2.2 are about twice as large. CPU/Rayon remains the only execution path.
- Cached window area fractions and outer-edge flags in per-call templates; cells clipped by tile boundaries are still computed directly. Edge distances use per-call tables wherever values provably match the per-edge calculation bit for bit, with direct calculation retained for other edges, including skewed or inconsistent level transforms and distances near an f32 rounding boundary. Distances and integer costs are unchanged.
- Batched BERI similarity calculations across all scenarios of each cell, preserving similarity values.
- Fixed each node's outgoing edge order: same-level queen neighbours in the existing order, then fringe links in
get_edge_neighboursorder. Shortest-path costs are unchanged, but integer-cost ties can select different parents and adjusted path sums, and indicator accumulation follows a different destination order. On the bundled example 2-16% of cells change, by up to about 1e-4 (2.4e-4 relative); grids where most edges round to zero integer cost can change more. Repeated runs and different thread counts remain bit-identical on the same build and platform. - Improved Rust
connectivity()validation: missing transforms are checked before processing, and skipped powers of two produce a clear panic message instead of an index error. Both previously surfaced when a focal cell first reached the affected level. Pythonconnectedness()andberi()already validate both. - Moved the Rust numerical engine (overviews, window templates, graph building, routing, indicator metrics and pixel coverage) into a separate crate,
connectivity-core0.1.0 incrates/connectivity-core, with a documented array-based Rust API and no Python, GDAL or file I/O dependencies. Therust_connextension is now a thin PyO3 wrapper around it: Python function signatures, outputs, exceptions and threading are unchanged. The core is versioned and tagged separately (connectivity-core-vX.Y.Z); onlyvX.Y.Ztags publish the Python package to PyPI.
- Reduced routing overhead by building one adjacency list for both adjusted and intact searches, caching raw adjusted-distance sums along the selected paths, and avoiding repeated path back-tracing and attribute copying. Integer cost scaling and shortest-path tie handling are unchanged.
- Replaced the remaining standard (SipHash) hash maps in per-cell graph building with
FxHashMap, and replacedpathfinding::dijkstra_allwith an in-crate Dijkstra that follows the same steps, preserving parents, ties, summation order and outputs.pathfindingis now only a test dependency. Together with the routing optimizations above, these changes made v2.3 about 2x faster than v2.2 in testing.
- Made indicator summation deterministic by retaining the adjusted search visitation order instead of iterating over a randomized hash map. Repeated runs on the same build and platform now use a fixed summation order without sorting targets for every focal cell. Final scores may differ in the last few bits from earlier versions because floating-point addition depends on order.
- Integer path costs are now checked for overflow. Inputs that push a path cost past the
u32range (for example a very largemax_costor unscaled resistance values) now raise an error instead of silently wrapping and corrupting the result.
- Added an optional
resistance_fileargument (andresistance_scale) toconnectedness()andberi(), backed by a newtraversalargument on the Rustconnectivity()binding. When supplied, the resistance raster is decoupled from condition and used only for path traversal — the edge weight becomesw = (1.0 - max_cost) * (1 - resistance) + max_cost— while condition still drives every indicator value (habitat area and the value multiplier). When omitted, condition is used for traversal as before, so output is unchanged. Resistance values are expected in[0, 1](scaled viaresistance_scale); cells valid in condition but missing resistance fall back to condition for traversal and emit a warning, so the analysis domain is never changed silently. - Added an optional
pa_to_paargument toconnectedness()(PARC edition, requirespa_file). When enabled, only graph nodes that fall on a protected area contribute to the connectedness numerator: paths still route through the intervening non-PA landscape (adjusted/intact distances unchanged) and the denominator keeps the full reachable area exactly as when disabled, so only the numerator changes — non-PA destinations contribute nothing and coarse cells straddling a PA boundary contribute only their protected fraction. A protected area that can reach no other scores 0. Defaults toFalse(original PARC behaviour). - Added an optional
filenameargument topixel_coverage(); when set, the per-pixel coverage array is also written to a GeoTIFF that inheritsraster_file's grid and CRS, producing a raster directly usable as thepa_fileinput toconnectedness(). - Added bundled example datasets and an
example_data_path()helper (exported from the top-level package) to resolve them by name —site_condition,pa_proportion, and thetransgrids/*climate scenarios — so the example notebooks and quick tests run without external downloads. - Added an example notebook,
examples/01_tasmania_indicators.ipynb, demonstrating the connectivity and BERI indicators on the bundled Tasmania data (shipped in the source distribution).
- Raised the minimum supported Python to 3.10 (
requires-python = ">=3.10"). - Updated packaging (
[tool.maturin]) to ship the example rasters in both wheels and source distributions, and the example notebooks in the source distribution. - Published the package on PyPI as
eco-connectivity(pip install eco-connectivity), since the nameconnectivityis taken there; it is still imported asconnectivity. Prebuilt wheels for Linux (x86_64, aarch64), macOS (Intel and Apple Silicon) and Windows (x64) mean installing no longer requires Rust. If you installed an earlier version from a locally built wheel, runpip uninstall connectivityfirst.
- Added
make_tile()andmake_tiles()helpers for creating non-overlapping raster-pixel core tiles whose internal row/column breaks align to a requested aggregation level, e.g.align_to=max(levels), for tiledconnectedness()andberi()runs. The helpers also support optional deterministic cost-balanced planning withbalanced=True.
- Fixed adjusted path distances to use the same
max_cost-scaled edge distances used by Dijkstra routing, avoiding a second hard-coded inverse-condition transform equivalent tomax_cost = 2. - Fixed geographic area weights in tiled runs to use tile-local rows with the already shifted read-window transform, avoiding a double row-offset that could leave residual horizontal tile seams.
- Added a
window_modeoption toconnectedness(),beri(), and the Rustconnectivity()binding. - Added
window_mode="circular"for source-centered circular annuli with fractional area/count support at annulus boundaries. - Added
window_mode="square"for source-centered square annuli. - Added
resolution_info()for previewing internally generated level dimensions, resolutions, cell counts, approximate native/km search reach, and small-grid warnings before choosinglevels.
- Changed the default window mode to
window_mode="circular". connectedness()andberi()now validate thatlevelsform a continuous power-of-two sequence before entering Rust graph construction.- Updated Rust Python bindings to
pyo30.28 andnumpy0.28, removing the deprecatedgil-refsandpy-clonefeature usage and migrating to the current PyO3/numpy APIs. - Updated the Python build configuration for current PyO3/maturin extension-module handling.
overview_info()is now a backward-compatible wrapper forresolution_info().- Reduced routing/window-construction overhead by avoiding per-node adjacency vector clones during Dijkstra, hoisting max-level lookup out of per-cell window construction, using faster hash maps for internal graph adjacency data, and writing parallel row results directly into the output array.
- Reduced Python-side overhead by reading BERI scenario rasters concurrently, avoiding an extra temporary array when converting masked raster data to
NaN-filledfloat32arrays, and applying theoption=3square-root transform in place.
- Removed
window_mode="block"and the original snapped multi-resolution window construction from the public API and Rust implementation. - Removed automatic grid-effect filtering and the
filter_kwargsarguments fromconnectedness()andberi(). - Removed
remove_grid_bias()from the package exports; - Removed the obsolete Rust
inpaint_nans_diffusionPython binding and implementation, which were only used by the previous FFT notch-filter path.
- Fixed a PARC-connectedness crash when the protected-area mask leaves no valid cells in the analysis window. Empty windows now return all-
NaNoutput instead of failing during filtering/inpainting. - Filtered bounded outputs from
connectedness()andberi()are now clipped back to the theoretical[0, 1]range after FFT notch filtering. This applies to connectedness (option = 1), connected habitat (option = 2), geometric-mean connected habitat (option = 3), PARC-connectedness, and BERI whenever filtering is enabled. - Float32 outputs written by
write_raster()now setnodata = NaNin the GeoTIFF metadata, and masked float arrays are written withNaNcells so the raster profile matches the stored data.
- Added
remove_grid_effect()as a standalone FFT notch-filter post-processing function (also exported from the top-level package import). - Added a Rust
inpaint_nans_diffusionimplementation (Rayon-parallel), with optional thread control vian_threads.
- Fixed seam artifacts caused by swapped row/column arguments in graph distance coordinate conversion (
Affine::xynow consistently receives(row, col)). - Fixed overview alignment across independently processed tiles by anchoring aggregation windows to global dataset pixel coordinates via tile offsets.
- Fixed cross-level edge-neighbour mapping to be offset-aware (global-anchor consistent) when linking a level to its higher level.
- Fixed occasional missing edge rows/cols in buffered tile reads by adding a half-pixel epsilon to the buffer extent.
- Graph construction now passes tile offsets through the full path (
core -> builder -> graph::fringe) to keep multi-resolution linking globally aligned. - Node IDs are now tile-invariant (
NodeId = u64from level/global-row/global-col), removing tile-local ID drift between adjacent runs. - Core traversal now processes overview levels in sorted order.
- Non-closed tile reads are snapped to the coarsest overview grid, improving consistency of aggregated counts/weights at tile boundaries.
- Added
Arearesampling for 2D overviews (latitude-weighted bycos(phi)), alongsideAverage,Sum, andCount. - 3D overview generation now explicitly supports only
AverageandSumresampling. overview_info()now reports estimated overview dimensions and does not require pre-existing file overviews.connectedness()andberi()now apply FFT notch filtering (remove_grid_effect) instead of the previous Gaussian smoother.- Filtering in
connectedness()andberi()is now controlled viafilter_kwargs: passNoneto disable filtering, or{}to apply default filtering (notch_width=3). - Inpainting used by filtering now relies on the Rust implementation and uses
n_threadswhen provided.
- Removed legacy
smoothing_filter()backward-compatibility wrapper. - Removed SciPy-based inpainting fallback and dropped the
scipyPython dependency.
- Incorporated habitat area into connectivity calculations using per-node cell counts (aggregate-based weighting).
- Integrated overview generation directly in Rust to eliminate preprocessing steps and reduce raster I/O bottlenecks.
- Added the
anyhowcrate for improved, centralized error handling. - Refactored core functionality into a dedicated module for improved structure and maintainability.
- Removed
create_overviews()andhas_overview(); overview handling is now fully internal.
- Fixed a bug for reading files with different overview levels.
- Added additional early checks for grid shapes and values.
- Added
closed_borderargument to control whether cells outside the mask are excluded from the analysis. - Fixed the polygon-masked output to not return the buffered array.
- Added compilation CPU optimisation flags (slower complie but faster run).
- Changed the
overview_levelsargument of thecreate_overviews()function tolevelsfor consistency. Also,file_pathinread_raster()function.
- Added
has_overview()function for checking overviews.
- A few internal structural changes; e.g., changes to the NumPy array memory layout and added structure in the Rust code for performance improvement.
- Fixed a bug involving a mismatch between conditions and transgrids for BERI by handling NANs.
- Replaced
Arc<T>withRc<T>reference counting, since atomic operations and thread sharing are not needed. - Improved code readability by adding more structs.
- Added PARC-connectedness calculation to the
connectedness()function. - Fixed a bug in reading transgrids with a polygon mask.
- Fixed the code to accept raster integer data type.
- Added a scaling factor (
scalearg) to convert condition data to the 0–1 range. - The output array/map is now cropped to the supplied polygon mask.
- Added type hints to the main functions.
- Made several improvements on the Rust side, such as using
Arc<T>for cheap cloning and introducing theGraphstruct for more organised code.
- Fixed isolated pixel calculation using their own values for a synthetic neighbour.
- Increased the capacity of the graph to take more neighbours for high-resolution rasters.
- Changed the default value of the
sigmaparameter to 1.0.
- Removed the dependency on GDAL, replacing it with
rasterio. - Updated logic to ignore overview level 1 (both when reading and generating) since it corresponds to the original resolution and requires no additional computation.
- Fixed the
read_raster()andcreate_overviews()functions to ensure that the correct overview layers are created and read properly.
- Added Euclidean and Haversine distances for projected and unprojected grids, respectively, instead of using cell units.
- Some changes in the arguments names including
max_cost,window_sizeandouter_window. - Some small fixes for graph calculation.
Added geographic distance (in km) instead of cell units Replaced the scale argument with max_cost Various minor improvements
- Initial version.