Benchmarks & accuracy¶
Landschaft makes two promises: the same results as the established tools, and much less waiting for them. This page shows how both are checked, with the numbers.
-
Same results
The tools are compared with the programs they reproduce: pixel for pixel, value for value, or to a stated tolerance where the original itself works in floating point. The exceptions are listed below.
-
Faster
C++ with OpenMP (and Eigen for the circuit solver): from a few times to hundreds of times faster than the originals.
-
Tested on every change
The comparisons live in the test suite: stored outputs of the originals, and live runs when the original is installed.
Speed¶

How much faster Landschaft is than the tool it reproduces, on the cases in the tables below (logarithmic scale). The original's time is its own run, including its input and output where noted; Landschaft is timed in memory.
How these were measured
The MSPA table was measured with tools/benchmark.py (version 0.53), and
the spatial prioritization table with tools/benchmark_prioritize.py, on a
desktop Intel Core i5-13600K (14 cores, 20 threads, 64 GB, Windows 11),
the fastest of 10 alternating runs, with both programs kept on the
performance cores. The other timings were recorded in the
changelog when each tool was written, with the
version given ("Since"). Times depend on the computer, the map and the options: treat them as
orders of magnitude.
Habitat patterns (MSPA)¶
Against GuidosToolbox's mspa executable (v2.3, whose time includes reading
and writing its TIFF files) on its sample map and on the CORINE forest map;
Landschaft with 1 thread and with the default 19. Every output was compared
pixel by pixel. Landschaft is 10 to 17 times faster.
| Map | Connectivity, edge width | GuidosToolbox | 1 thread | 19 threads | Identical |
|---|---|---|---|---|---|
| Sample, 1000² | 8, 1 | 0.46 s | 0.040 s | 0.029 s | yes |
| Sample, 1000² | 8, 5 | 0.56 s | 0.065 s | 0.054 s | yes |
| Sample, 1000² | 4, 20 | 0.68 s | 0.079 s | 0.068 s | yes |
| CORINE, 2000² | 8, 1 | 1.30 s | 0.095 s | 0.078 s | yes |
| CORINE, 2000² | 8, 5 | 1.53 s | 0.151 s | 0.137 s | yes |
| CORINE, 2000² | 4, 20 | 1.70 s | 0.213 s | 0.174 s | yes |
At scale, the CORINE map repeated 3 × 3 (6000 × 6000, 36 million pixels) takes GuidosToolbox 20 to 26 s and Landschaft 1.4 to 2.3 s, again with identical outputs.
MSPA's longest steps (geodesic distances, thinning, watershed) are ordered
propagations that must run in sequence to stay bit-exact, so one map takes
only 10 to 30 % less time with more threads. Many maps gain fully: run them side by side with
mspa_batch or
batch.
Hybrid processors
On processors with performance and efficiency cores (Intel 12th generation and later), Windows may move a busy process to the slower efficiency cores, especially when it runs in the background; any program then takes up to twice as long. Keep the terminal in the foreground for timings, and compare runs made side by side.
Landscape metrics¶
Against the R package landscapemetrics 2.2.2.
| Task | landscapemetrics (R) | Landschaft | Since |
|---|---|---|---|
All 133 metrics of augusta_nlcd (440 × 678) |
15.6 s | 0.03 s | 0.14 |
sample_lsm, 6 plots of augusta_nlcd |
3.7 s | 0.02 s | 0.15 |
Nearest-neighbour distances (enn) of augusta_nlcd |
2.4 s | 0.014 s | 0.14 |
get_nearestneighbour, 1000 × 1000 |
1.8 s | 0.05 s | 0.18 |
At scale: a 10,000 × 10,000 map with 7.7 million patches gets all 133 metrics (92 million values) in about 10 s, and maps larger than memory are read in blocks with the same results.
Habitat networks¶
Against the R package Makurhini (and, through it, gdistance).
| Task | Makurhini (R) | Landschaft | Since |
|---|---|---|---|
IIC and node importances, 404 patches (MK_dPCIIC) |
1.4 s | 0.005 s | 0.30 |
PC and node importances, 404 patches (MK_dPCIIC) |
10.6 s | 0.017 s | 0.30 |
| Least-cost distances, 398 patches over 222 k cells | 10.2 s | 0.5 s | 0.32 |
| Commute-time distances | 13 × faster | 0.39 |
At scale: the 35 million centroid distances between 8,395 patches of a 4000 × 4000 map take 0.9 s, and all 4.3 million links of a 5,000-patch PC graph are rated in 22 s.
Circuit connectivity¶
Against Omniscape 0.7 and Circuitscape 6.1 (Julia), both with 20 threads.
| Task | Original | Landschaft | Since |
|---|---|---|---|
| Omniscape, 800 × 800, radius 10 | 29.4 s | 0.64 s | 0.43 |
| Omniscape, 700 × 700, radius 25 | 46.7 s | 1.36 s | 0.43 |
| Omniscape, 1000 × 1000, radius 100 | 275 s | 12.4 s | 0.43 |
| Circuitscape pairwise, 1000 × 1000, 20 focal points | 8.7 s | 2.7 s | 0.48 |
| … with the cumulative current map | 76.5 s | 9.5 s | 0.48 |
| Circuitscape one-to-all with maps, same map | 21.8 s | 4.6 s | 0.48 |
| Circuitscape network pairwise, 1 M nodes, 20 focal nodes | 27.7 s (CHOLMOD), 182 s (default cg+amg) | 5.0 s | 0.51 |
Every window and every circuit is solved exactly (a sparse Cholesky factorisation), and the result does not depend on the number of threads.
Grid distances¶
Against the R package gdistance 1.6.5 on a 300 × 300 grid of random resistances (8 directions; best of 3 runs in R, of 5 in Landschaft).
| Task | gdistance (R) | Landschaft | Since |
|---|---|---|---|
Transition (1 / mean) and geographic correction |
2.0 s | 0.005 s | 0.75 |
Cost surface from one point (accCost) |
0.10 s | 0.011 s | 0.76 |
| Least-cost distances between 20 points | 0.31 s | 0.026 s | 0.76 |
| Least-cost paths to 5 goals | 0.05 s | 0.012 s | 0.76 |
| Commute distances between 10 points | 0.29 s | 0.084 s | 0.77 |
| Random-walk passages | 0.36 s | 0.066 s | 0.77 |
Randomised-shortest-path passages (theta = 0.5) |
5.98 s | 0.078 s | 0.80 |
| Randomised-shortest-path distances, 3 × 3 points | fails (a 60 GiB dense matrix) | 0.15 s | 0.80 |
| … the same on 60 × 60 | 0.23 s | 0.0043 s | 0.80 |
| Route overlap from a common origin, 10 locations | 5.97 s | 0.16 s | 0.79 |
At scale: on 2000 × 2000 (4 million cells) a transition takes 0.15 s, a cost surface 0.6 s, a random-walk passage map 4.5 s, a randomised-shortest-path passage map 4.6 s (7.8 s for an asymmetric transition).
Neutral landscape models¶
The same landscapes as NLMR (same seeds, identical values), one model call each (best of 5, default threads).
| Task | NLMR (R) | Landschaft | Since |
|---|---|---|---|
nlm_random, 2000 × 2000 |
0.25 s | 0.044 s | 0.54 |
nlm_percolation, 1000 × 1000 |
1.54 s | 0.014 s | 0.54 |
nlm_planargradient, 2000 × 2000 |
0.25 s | 0.017 s | 0.54 |
nlm_edgegradient, 2000 × 2000 |
0.31 s | 0.020 s | 0.54 |
nlm_distancegradient, 2000 × 2000 |
0.25 s | 0.10 s | 0.54 |
nlm_distancegradient, 360 × 90 (great-circle distances) |
0.06 s | 0.011 s | 0.54 |
landscape_classify by proportions, 1000 × 1000 |
0.05 s | 0.019 s | 0.54 |
nlm_mpd, 1025 × 1025 |
0.13 s | 0.043 s | 0.55 |
nlm_neigh, 500 × 500 |
0.04 s | 0.023 s | 0.55 |
nlm_randomrectangularcluster, 300 × 300, sides 5–20 |
0.02 s | 0.003 s | 0.55 |
nlm_randomcluster, 200 × 200 |
19 s | 0.003 s | 0.56 |
nlm_curds, 512 × 512 (three steps) |
0.03 s | 0.003 s | 0.56 |
Gaussian field (simulate_gaussian_field), 500 × 500 |
0.36 s | 0.043 s | 0.57 |
Fractional Brownian motion (simulate_fbm), 1000 × 1000 |
0.40 s | 0.050 s | 0.57 |
nlm_mosaicfield, 200 × 200, 20 mosaics |
9.95 s | 0.010 s | 0.58 |
nlm_mosaicfield, 100 × 100, 20 mosaics and the limiting field |
9.0 s | 0.008 s | 0.58 |
nlm_mosaictess, 500 × 500, 1000 germs |
0.25 s | 0.010 s | 0.59 |
nlm_mosaicgibbs, 500 × 500, 500 germs |
0.50 s | 0.007 s | 0.59 |
Surface metrics¶
Against geodiv 1.1.1 and spatialEco 2.0-5 (R 4.4.2, terra 1.9-50) on geodiv's
example rasters (371 × 371 cells: normforest, orforest for the trend,
orelevation for terrain), measured with tools/benchmark_surface.py: the
best of 3 runs in R (fast tasks repeated to 0.5 s), of 5 in Landschaft with the
default threads. Where Landschaft corrects geodiv or spatialEco the work is the
same.
| Task | R | Landschaft | Since |
|---|---|---|---|
| Amplitude (sa, sq, ssk, sku) | 29 ms | 3.9 ms | 0.85 |
| Best trend surface of order 0-3 | 0.15 s | 25 ms | 0.85 |
| Slopes and developed area (sdq, sdq6, sdr) | 2.16 s | 4.8 ms | 0.85 |
| Peaks and valleys (sds, ssc, s10z) | 1.84 s | 1.7 ms | 0.85 |
| Bearing area parameters (sbi, sci, svi, sk, spk, svk) | 0.88 s | 11 ms | 0.85 |
| Correlation lengths and aspect ratio (scl, stxr) | 0.13 s | 28 ms | 0.85 |
| Texture direction and wavelength (std, srw) | 0.61 s | 28 ms | 0.85 |
| Fractal dimension (sfd) | 6.3 ms | 2.2 ms | 0.85 |
Texture image, Sa in 5 × 5 windows, 100 × 100 (focal_metrics) |
0.17 s | 1.0 ms | 0.85 |
Texture image, Sk in 5 × 5 windows, 30 × 30 (focal_metrics) |
134 s | 22 ms | 0.85 |
| TPI, 5 × 5 | 8.5 ms | 2.5 ms | 0.88 |
| TRI | 0.97 s | 1.9 ms | 0.88 |
| VRM | 80 ms | 15 ms | 0.86 |
| Curvature (total) | 1.58 s | 1.6 ms | 0.88 |
| Hierarchical slope position, scales 3-27 | 0.11 s | 14 ms | 0.88 |
| Spherical standard deviation | 73 ms | 12 ms | 0.86 |
The gains are largest where the R packages compute cell by cell in R (texture images, peaks, curvature, TRI) and smallest where terra's focal functions do the work in C++ with running sums (TPI, HSP, VRM). Landschaft's focal means, sums and standard deviations use compensated prefix sums along the rows (within about 1e-13 of summing each window), so a window costs one step per row whatever its size.
Terrain hydrology¶
Against WhiteboxTools 2.4 on a rough terrain of 3000 × 3000 cells, both on 4
cores (tools/benchmark_hydro.py, best of 3); WhiteboxTools' times include
reading and writing its GeoTIFFs, so Landschaft's include writing its result.
| Task | WhiteboxTools | Landschaft | Since |
|---|---|---|---|
| Filling depressions | 3.16 s | 2.07 s | 1.48 |
| Least-cost breaching (channels up to 20 cells) | 3.86 s | 2.35 s | 1.48 |
| D8 flow accumulation | 1.95 s | 0.62 s | 1.48 |
| D-infinity flow accumulation | 1.84 s | 1.22 s | 1.48 |
| FD8 flow accumulation | 2.00 s | 1.65 s | 1.48 |
Spatial prioritization¶
Against Zonation 5 v2.4 (z5.exe, and z5_16bit.exe for 16 bits) on
synthetic species maps: seeded Gaussian random fields above a threshold, each
feature on 60 % of the cells (10 % in the 200-feature case), written as
GeoTIFFs. Measured with tools/benchmark_prioritize.py (version 0.95) on the
performance cores, one run of z5 and the fastest of 3 of Landschaft; both
times include reading the GeoTIFFs, and z5's writing its outputs. Every rank
map is the same, bit for bit, as z5's on one thread.
| Map | Features | Rule | Iterations | z5, 1 thread | z5, 12 threads | Landschaft, 1 thread | Landschaft, 12 threads | Since |
|---|---|---|---|---|---|---|---|---|
| 1000 × 1000 | 30 | CAZ2 | 60 | 20.6 s | 12.4 s | 9.5 s | 1.86 s | 0.95 |
| 2000 × 2000 | 30 | CAZ2 | 60 | 102.8 s | 48.0 s | 45.6 s | 7.29 s | 0.95 |
| 2000 × 2000 | 30 | ABF | 20 | 53.6 s | 23.4 s | 36.5 s | 5.90 s | 0.95 |
| 2000 × 2000 | 30 | CAZ2, 16 bits | 60 | 104.4 s | 49.7 s | 46.2 s | 8.71 s | 0.95 |
| 1000 × 1000 | 200 sparse | CAZ2 | 59 | 105.8 s | 33.6 s | 15.0 s | 3.16 s | 0.95 |
On one thread Landschaft is 1.5 to 7 times faster than z5, most with many
sparse features: each feature is walked along its own occurrences, where z5
visits every feature in every cell. With 12 threads the gap grows to 4 to 11
times: z5 gains 1.7 to 3 times from its 12 threads, Landschaft 4.7 to 6.3. Memory: about 130 bytes per
cell with data during the ranking, plus 1 to 2 bytes per value that is not
zero; storage= puts the large arrays in memory-mapped files.
Optimized plans¶
Against prioritizr 8.0.6's models solved by HiGHS 1.15 alone (the solver
prioritizr calls) and by Gurobi 12, on synthetic problems of
tools/benchmark_optimize.py: 30 species-like features with targets of 30 %
and costs from 1 to 10 (multi-action: 10 features, 3 threats), a gap of 1 %,
12 threads on the performance cores. "No answer" means no plan within the
time limit (120 s unless stated). The same models and the same optima as
prioritizr's; Landschaft's engines start HiGHS from near-optimal plans, fix
the units whose choice is proven, and solve the problems general solvers
find hard on their own.
| Problem | Units | HiGHS alone | Gurobi | Landschaft | Since |
|---|---|---|---|---|---|
| minimum set | 10⁵ | 7 s | 2 s | 0.44 s | 1.21 |
| minimum set | 10⁶ | no answer (its best plan 2.1 x the optimum) | 20 s | 1.7 s (gap 0.002 %) | 1.21 |
| minimum set | 10⁷ (5 features) | 60 s, proven optimal | 1.26 | ||
| boundary penalty | 10⁶ | no answer in 16 min | 289 s | 27 s (gap 0.77 %) | 1.21 |
| minimum penalties with boundary, gap 10 % | 2400 | more than 150 s | 12 s | 1.27 | |
| multi-action, least cost | 10⁵ | 57 s | 3 s | 0.7 s | 1.22 |
| multi-action, least cost, gap 0 | 10⁵ | no answer in 300 s | 13 s | 6 s | 1.22 |
| multi-action with boundaries | 10⁴ | 7.5 s | 0.18 s | 1.22 | |
| multi-action with river dependencies (soft / hard) | 10⁵ | 6 s / 8 s | 1 s / 5.5 s | 1.22 |
Contiguity constraints are the exception. prioritizr's contiguity model is a restriction (it misses some patches, see the migration guide); Landschaft solves the exact model, starting from a patch of the restricted one and with directed cuts added before the branch and bound. On prioritizr's contiguity test problems (166 units, a raster and a table) it proves a gap of 5 % in 1 to 1.5 s and finds patches as cheap as or cheaper than prioritizr's optimum. On larger problems the bound stays weak without cuts inside the branch and bound: on Serra Verde at 3 km (543 units, 8 features) Landschaft's network after 120 s is 4.6 % above the best Gurobi finds in 600 s, with a proven gap of 17 % (Gurobi's, 3.8 %).
Species distributions¶
Against the R packages each step reproduces (R 4.4.2, terra 1.9-50):
CoordinateCleaner 3.0.1, spThin 0.2.0, usdm 2.1-7, dismo 1.3-16, blockCV
3.2-0, predicts 0.1-19, ecospat 4.1.3 and maxnet 0.1.4 with glmnet 4.1-10.
The inputs are the example climate (9 bioclimatic layers, 192 × 186 cells of
0.5°), the Bradypus records with 10 000 background points, 5 000 synthetic
records over the same area and 2 000 clustered ones. Measured with
tools/benchmark_sdm.py (version 1.0) on the performance cores: the best of
3 runs in R (fast tasks repeated to 0.5 s), of 5 in Landschaft with the
default threads. Every result is the same as R's, the random steps included
(same seed), except where the notes on accuracy below say otherwise.
| Task | R | Landschaft | Since |
|---|---|---|---|
| Clean 5 000 records: capitals, centroids, equal, GBIF, institutions, outliers, zeros (CoordinateCleaner) | 1.99 s | 0.16 s | 1.0 |
| Thin 2 000 records at 10 km, 10 repetitions (spThin) | 0.91 s | 1.4 ms | 1.0 |
VIF selection, 9 layers (usdm vifstep) |
0.17 s | 16 ms | 1.0 |
10 000 background points (dismo randomPoints) |
0.43 s | 12 ms | 1.0 |
Hexagonal blocks of 500 km, 10 116 points, 100 iterations (blockCV cv_spatial) |
1.77 s | 53 ms | 1.0 |
k-means folds, 10 116 points, 25 starts (blockCV cv_cluster) |
93 ms | 14 ms | 1.0 |
Autocorrelation range, 9 layers, 5 000 cells each (blockCV cv_spatial_autocor) |
17.5 s | 0.73 s | 1.0 |
Evaluate 1 000 presences against 10 000 background points (predicts pa_evaluate) |
0.19 s | 8.3 ms | 1.0 |
| Boyce index, the same predictions (ecospat) | 9.8 ms | 1.6 ms | 1.0 |
| MESS, 9 layers, 10 116 reference points (predicts) | 0.75 s | 19 ms | 1.0 |
| MaxNet, 116 presences and 10 000 background points, 9 predictors (maxnet) | 3.25 s | 0.30 s | 1.0 |
The largest gains are where R works on whole matrices: spThin computes the distance between every pair of records and rescans them after each removal (Landschaft keeps only the neighbours, in a grid over the sphere, and the counts in a tree: a million records take seconds, where R runs out of memory), and blockCV's blocks and predicts' evaluation go through data frames row by row. MaxNet keeps glmnet's arithmetic and order of operations, so the gain comes from screening (most gradient checks are settled by a bound, without the exact dot product) and from parallel work around the coordinate descent, which itself stays serial; with 20 predictors and hinge features (10 000 background points) it fits in 0.47 s against R's 6.4 s. Weighted sampling without replacement, which R does in time proportional to the cells times the draws, runs in closed form over runs of equal weights (1 million cells and 30 000 draws: 0.24 s against 16-20 s with R's algorithm).
Land change simulation¶
Against the land change tools ls.simulate reproduces, on the cases of its
reference tests. Measured with tools/benchmark_simulate.py (version 1.20):
lulcc 1.0.4 in R 4.4.2 (with the corrections of tools/lulcc_fixed.R, so
that both sides make the same allocation; only the timed call is measured),
MOLUSCE 5.3.0's algorithms in Python, Dinamica EGO 8.13's console (the time
of a script that only loads the maps is subtracted); the best of 3 runs, the
default threads. CLUMondo and FUTURES were timed when they were written.
| Task | Original | Landschaft | Since |
|---|---|---|---|
CLUE-S allocation, Plum Island, 14 steps (lulcc allocate) |
36.4 s | 4.12 s | 1.8 |
Ordered allocation, Plum Island, 14 steps (lulcc allocate) |
2.9 s | 0.40 s | 1.8 |
| Land systems, 400 × 500 cells, 8 years (CLUMondo) | 16.2 s | 1.7 s | 1.9 |
Urban growth, 600 × 800 cells, 5 steps (r.futures.simulation) |
0.35 s | 0.20 s | 1.10 |
| Weights of evidence and 3 simulator iterations, 180 × 240 (MOLUSCE) | 1.99 s | 53 ms | 1.15 |
| Three-map comparison, agreement budget and figure of merit, Plum Island, 6 resolutions (lulcc) | 7.21 s | 0.17 s | 1.17 |
| Fuzzy similarity of the differences, windows 1-11, 480 × 640 (Dinamica EGO) | 0.67 s | 49 ms | 1.18 |
Gravity potential, 1000 × 1000, 1000 sources (Dinamica EGO CalcInteractionPotentialMap) |
0.42 s | 0.13 s | 1.46 |
Stock taken by patches, 3000 × 3000, 50 000 cells (Dinamica EGO PatcherContinuousState) |
3.8 s | 0.59 s | 1.46 |
Cost surface to a road network, 3000 × 3000 (Dinamica EGO CalcCostMap, passes until exact) |
769 s | 1.4 s | 1.46 |
The Dinamica EGO functors of version 1.46 were timed with
tools/benchmark_dinamica.py on 4 cores on both sides (Dinamica with
-processors 4), the time of the functor in Dinamica's log. Its cost surface sweeps the map in four directions until no cost
changes (fewer passes give an approximation); Landschaft's runs Dijkstra's
algorithm once, with the same sums.
CLUE-S draws a random number per cell and class at each of its thousands of passes, from R's generator, one after the other: Landschaft draws the same numbers, so the gain is in the rest of the pass. The three-map comparison runs lulcc's equations per block in one pass of C++ (R builds a raster per class and equation), and MOLUSCE loops over the pixels in Python.
Zonal statistics and extraction¶
Not a reproduction of another tool: the functions that read a raster in zones
and at points (zonal_stats, extract), against the Python packages commonly
used for it, rasterstats 0.21 and exactextract 0.3 (both single-threaded). A
5000 × 5000 float32 raster in memory; the statistics mean, min, max, standard
deviation and median; random polygons of 16 vertices. Measured with
tools/benchmark_zonal.py (version 0.98) on the performance cores, the
fastest of 3 alternating runs.
| Case | rasterstats | exactextract | Landschaft, 1 thread | Landschaft, 12 threads | Since |
|---|---|---|---|---|---|
| zonal statistics, 2000 small polygons (16 million cells) | 2.34 s | 3.59 s | 0.37 s | 0.16 s | 0.98 |
| zonal statistics, 20 large polygons (22 million cells) | 1.14 s | 8.37 s | 0.37 s | 0.18 s | 0.98 |
| values at 1 million points | ~119 s | – | 0.028 s | 0.014 s | 0.98 |
The cells of every polygon are those GDAL burns (the centre rule, or
all_touched), as rasterstats takes them: the statistics agree with its own,
which adds up in single precision, to 1e-7, and with NumPy in double precision
to 1e-15. exactextract weights every cell by the share of it the polygon
covers, so its means differ slightly. The median and the percentiles take a
linear-time selection, not a sort; the result does not depend on the number of
threads.
Plotting¶
ls.plot, from the data to the page a notebook or a window shows (the map's
HTML, images and data included), measured with tools/benchmark_plot.py
(version 1.32.0) on the performance cores, the fastest of 3 runs. A first plot
in a session also imports folium and compiles its templates, about 0.7 s once.
A page holds everything it needs (Leaflet, about 0.16 MB; plotly, 4.3 MB, in
a page with charts), so it opens without a network connection.
| Case | Time | Page |
|---|---|---|
| float32 raster, 10 000 × 10 000, in memory | 0.25 s | 4.7 MB |
| class raster, 10 000 × 10 000, in memory | 0.14 s | 1.6 MB |
| float32 raster, 10 000 × 10 000, read lazily from a file | 0.61 s | 4.7 MB |
| float32 raster, 2000 × 2000 | 0.23 s | 5.1 MB |
| stack of 47 bands of 1000 × 1000 (a band per thread, 16 million pixels in all) | 0.85 s | 14 MB |
| 10 000 polygons coloured by a column (GeoJSON) | 0.14 s | 2.7 MB |
| 50 000 polygons coloured by a column (WebGL) | 0.36 s | 1.5 MB |
| 200 000 polygons coloured by a column (WebGL, with their columns) | 0.99 s | 5.3 MB |
| 2 million points coloured by a column (WebGL) | 0.70 s | 13 MB |
50 000 polygons, static figure (static=True, drawn as a PNG) |
0.57 s | 0.7 MB |
| 200 000 polygons, static figure (burnt into an image) | 0.80 s | 0.6 MB |
| a table of landscape metrics (a chart) | 0.03 s | 4.3 MB |
The time hardly grows with the size of a raster: it is reduced to the resolution of the screen first (block means in C++, or the cells of a lazy raster read in strips), and only that is reprojected and encoded.
Accuracy¶
How results are compared¶
- Stored references. The outputs of the original tools on the example
maps, on synthetic maps built to hit edge cases, and on the originals' own
test cases are stored in
tests/data/reference. Every run of the test suite compares with them. - Live runs. When the original is installed, the tests also run it on
freshly generated random maps and compare on the spot
(
pytest -m live_reference). - Original code, not a re-reading of it. Where GuidosToolbox computes in
IDL, its processing statements are copied verbatim from
guidostoolbox.proand run with GDL, the free IDL implementation, with GuidosToolbox's ownspatcon,grayspatcon,recodeandggeoprograms where it calls them. - Bits, then tolerances. Integer maps and counts must be identical.
Floating-point results are compared bit for bit where the original's
arithmetic can be reproduced (landscapemetrics: R's 80-bit sums and x87
log/sqrt), and otherwise to the tolerance given below, typically the precision of the original itself.
Results by tool¶
| Tools | Compared with | Agreement |
|---|---|---|
| MSPA | GuidosToolbox mspa v2.3 executable: sample map with 7 parameter sets, 10 synthetic scenes, live random scenes; during development 1,200 random cases and CORINE with edge widths up to 100 |
identical, 0 differing pixels |
| MSPA Tiling | GuidosToolbox's IDL code with its GDAL tools and mspa, in GDL |
identical |
| SPA, Accounting | GuidosToolbox's IDL code in GDL | identical maps and statistics |
| FAD, FAD-MS, FOS, landscape mosaic, contagion and clustering | GuidosToolbox's IDL code and spatcon / grayspatcon / recode |
identical maps; single-precision means and standard deviations to 1e-4 (Landschaft computes in double) |
| FAD-MS change, landscape mosaic heatmap and heatmap change | GuidosToolbox's IDL code in GDL | identical |
| Isochrone maps A and AB, least-cost path | GuidosToolbox's ggeo and IDL post-processing |
identical |
| 133 landscape metrics and the utilities | landscapemetrics 2.2.2 (R): 14 maps (its example data, NA cells, non-square cells, degenerate layouts) × 5 option sets, live random maps | identical bits; gyrate and rare enn ties to a relative 1e-10; circle is randomised in landscapemetrics |
FRAGSTATS extras, convention="fragstats", spatial signatures |
pylandstats, scikit-learn, FRAGSTATS' definitions | same values |
| Habitat graphs: indices, node and link importances, centrality | Makurhini (MK_dPCIIC, MK_dPCIIC_links, MK_RMCentrality) and Conefor Sensinode 2.2 / 2.7.1 |
Makurhini: unrounded values to 1e-12, its rounded output exactly; Conefor: to its single precision (6 digits) |
| Nodes and distances (centroid, edge, least-cost, commute-time) | Makurhini's nodesfile / distancefile (gdistance) |
identical bits; commute-time to 1e-11 |
| ProtConn, grids, ECA change, focal nodes | Makurhini (MK_ProtConn, MK_Connect_grid, MK_dECA, MK_dECA_grid, MK_Focal_nodes) |
its rounded tables exactly; ECA change to 1e-13 |
| Settlement probabilities (SAMC) | samc and Makurhini's MK_dPC_SAMC |
1e-14 |
| Omniscape | Omniscape 0.7 with the CHOLMOD solver: its test configurations and synthetic maps | 1.7e-11 |
| Circuitscape raster modes | Circuitscape 6.1 with CHOLMOD: its own test cases (advanced, 17 pairwise, 25 one-to-all / all-to-one) | advanced: currents to 1.2e-13 of the largest; pairwise: resistances to 1e-12, maps to 3e-13; one-to-all: 5e-14 |
| Circuitscape network mode | Circuitscape's network test cases and six synthetic ones | 1.1e-14 |
| Grid distances: transitions (4/8/16 directions, symmetric or not, R's arithmetic, Mahalanobis), geographic correction (projected, longitude/latitude, global grids), normalisation, arithmetic, cost surfaces, least-cost distances and paths | gdistance 1.6.5 (R 4.4.2, raster 3.6-31, igraph 2.1.3): 53 reference cases, among them uniform grids where many paths cost the same | identical bits and the same paths |
| Grid distances: commute distances, random-walk and randomised-shortest-path passages and distances, route overlap, barrier and area transitions | the same gdistance: 15 reference cases | 1e-14 typical, 1e-10 worst (different sparse factorisations than R's Matrix package); barrier and area transitions identical |
Surface metrics: amplitude parameters (sa, sq, ssk, sku, sv, sph, smean), trend surfaces of order 0-3 and the choice of the best one, slopes and developed area (sdq, sdq6, sdr), peaks, valleys, sds, ssc, s10z, the bearing area curve and its parameters (sdc, sbi, svi, sci, sk, spk, svk), autocorrelation, correlation lengths, texture direction and wavelength, fractal dimension, texture images |
geodiv 1.1.1 (R 4.4.2, terra 1.9.50, spatial 7.3-17), and where geodiv errs geodiv with its errors corrected (tools/surface_fixed.R): 70 reference cases, projected and longitude/latitude, among them geodiv's example rasters, NA cells, non-square cells and ties |
identical bits |
| Terrain indices: slope, aspect, tpi, tri, vrm, srr, dissection, curvature, spherical standard deviation, hsp, trasp, hli | spatialEco 2.0-5 and terra 1.9-50 (R 4.4.2), and where spatialEco errs spatialEco with its errors corrected (tools/surface_fixed.R): 3 DEMs (square and rectangular cells, missing cells, longitude/latitude) |
identical bits; focal means (tpi, hsp, srr, vrm, spherical standard deviation, Bolstad's curvature) within 1e-12 of summing each window (compensated prefix sums), 1e-11 of terra's running sums; on longitude/latitude slope and aspect within 1e-12 |
| Terrain hydrology: least-cost breaching, D8 and D-infinity directions, D8, D-infinity and FD8 accumulations (cells, specific contributing area), HAND, wetness, stream power and sediment transport indices | WhiteboxTools 2.4: 4 DEMs (SRTM, missing cells, rectangular cells, terraced elevations with many flats and ties) | identical bits (the accumulations and indices to the float32 WhiteboxTools writes); filling and breaching identical to FillDepressions and BreachDepressionsLeastCost (on one thread) run with the map's edge as an outlet (WhiteboxTools raises depressions that touch the edge or missing cells, and leaves channels to the edge uncut) |
Terrain hydrology: multiple flow routing ("mfd"), distance to channel, stream tracing, its D8 routing, pit filling |
pygeoprocessing 2.4.11 (the routing of InVEST 3.20): the same 4 DEMs, plain and weighted | identical bits |
| Spatial prioritization: the ranking (8 rules, weights and weight groups, output groups, thresholds, zero modes, value extraction, costs, hierarchic masks, analysis areas, smoothing, 8 and 16 bits), condition, retention, pre-processing maps, transformed layers, connectivity transforms, post-processing, impact assessment | Zonation 5 v2.4 (z5.exe, z5_16bit.exe, on one thread): 64 reference projects, and the timing cases below |
identical rank maps and iterations; curve, statistics, post-processing and impact files digit for digit |
| Optimized prioritization: every objective, target, penalty, constraint, decision and portfolio of prioritizr; raster, table and polygon planning units | prioritizr 8.0.6 (R 4.4.2; its errors corrected: phylogenetic tip order, portfolio cuts): compile() of 55 reference problems; the optimum with Gurobi |
identical models, row by row and bit for bit; the same optima |
| Evaluation: summaries, representation, target coverage, Ferrier, rarity-weighted richness, replacement and rank importance; calibration | prioritizr 8.0.6 and its calibration vignette | identical bits (R's pnorm ported) |
| Multi-action planning | prioriactions 0.5.0 (its errors corrected): getModelInfo of 6 reference problems, costs, benefits, actions |
identical models and figures; the same optima as Gurobi; dependencies and portfolios tested against exhaustive enumeration |
| Polygon planning units | exactextractr 0.10.0 (exact_extract(fun = "sum")) |
identical cell fractions and sums |
| Marxan | Marxan 4.0.6 (its errors corrected), the same seeds | identical runs: solutions, scores, summaries |
| Species distributions: occurrence cleaning (all 13 tests, records on circle vertices, the antimeridian and poles), thinning, one record per cell | CoordinateCleaner 3.0.1 (its errors corrected: tools/cc_fixed.R), spThin 0.2.0, terra 1.9-50: 14 reference cases |
identical flags and kept records; the circles' vertices identical (GeographicLib 2.7 and 1.52 with mingw's x87 maths) |
| Species distributions: predictor selection, background points, spatial blocks and clusters, ENMeval partitions | usdm 2.1-7, dismo 1.3-16, blockCV 3.2-0 (its own k-means: R's kmeans, 48 cases), ENMeval 2.0.5.2: 53 reference cases with R's generator |
identical bits, random steps included |
| Species distributions: evaluation, Boyce index, omission rates, MESS, MOP | predicts 0.1-19, ecospat 4.1.3 (its errors corrected: tools/ecospat_fixed.R), ENMeval, mop 0.1.4: 16 reference cases |
identical bits; the p-value of the correlation to 1e-9 (R's pt) |
| Species distributions: Mahalanobis distances (MOP, ExDet), autocorrelation range | mop, dsmextra 1.1.6 (its covariate index corrected), blockCV cv_spatial_autocor (automap, gstat 2.1-6) |
1e-9 (R inverts the covariance with LAPACK); variograms identical, fitted ranges to 1e-6 |
| MaxNet | maxnet 0.1.4 with glmnet 4.1-10: 13 fits (Bradypus and synthetic, every feature class, regularisation multipliers, factors) | identical bits: coefficients, lambda, entropy and the four output types, on Windows and Linux |
| Neutral landscape models: random, percolation, gradients, midpoint displacement, fractional Brownian motion, Gaussian fields, Poisson line and Voronoi mosaics, random, neighbourhood and rectangular clusters, curds, classification | NLMR 1.4.0 (R 4.4.2) with the same seeds; R's generator and the libstdc++ distributions of NLMR's C++ code against R and the Rtools compiler | identical bits |
| Land change analysis: cross tables, intensity analysis, components of difference, multiple resolutions, Markov chains | lulcc 1.0.4, OpenLand 1.0.5, diffeR 0.0-8 (R 4.4.2; their errors corrected, tools/differ_fixed.R): 11 cases; Dinamica EGO 8 transition matrices: 6 cases |
identical bits; Dinamica's matrices to 2e-11 (it writes 15 digits; its root of the matrix is less exact) |
| Land change: weights of evidence (ranges, weights, correlations, probabilities) | Dinamica EGO 8.13 (its code is closed: inferred from its outputs): 9 cases, 84 range probes | ranges and counts identical, weights to the 15 digits it writes |
| Land change: logistic regression, CLUE-S and ordered allocation, neighbourhoods, rules | R's glm and lulcc 1.0.4 (its errors corrected: tools/lulcc_fixed.R): 9 cases |
identical bits, R's random numbers included |
| Land change: land systems (CLUMondo) | CLUMondo (GitHub a5c0c1f, built Win32 with MSVC; its age error corrected: tools/clumondo_fixed.patch): 6 cases; the May 2017 executable for its later options |
identical maps and iteration logs; 97-100 % of the cells for the 2017 options (no source) |
| Land change: urban growth (FUTURES): simulation, flooding, demand, pressure, patch library and calibration | r.futures addons in GRASS 8.3.2 (their errors corrected: tools/futures_fixed.patch): 16 cases; the potential's mixed model against lme4 2.0.1 and MuMIn 1.48 |
identical maps and tables; mixed-model coefficients to 2.6e-6 (an optimiser: lme4 stops at its tolerance) |
| Land change: MOLUSCE's weights of evidence, simulator, multi-criteria evaluation, kappa and error budget | MOLUSCE 5.3.0 imported without QGIS (its confidence corrected: tools/molusce_fixed.patch): 6 cases |
identical bits, NumPy's random numbers included |
Land change validation: three maps at several resolutions, agreement budget, figure of merit, r.futures.validation |
lulcc 1.0.4 (raster 3.6-31): 2 cases; GRASS's addon: 3 cases | identical bits |
| Land change validation: fuzzy similarity and similarity of differences | Dinamica EGO 8.13 (measured): 36 maps, windows 1-11, exponential and constant decay | identical maps; means to the 15 digits it writes |
Land change: gravity potential (surface.gravity) |
Dinamica EGO 8.13 CalcInteractionPotentialMap (measured): 10 maps, five exponents, with and without a mask |
within 3e-6 (Dinamica adds float32 terms in float32; Landschaft sums in float64) |
Land change: roads (friction_cost, pathways) |
Dinamica EGO 8.13 CalcCostMap and CalcPathwayMap (measured): real and integer frictions, with and without diagonal costs, relative frictions, impassable and unreachable cells; paths down the costs and along the directions |
identical costs and paths; directions identical except between equally cheap predecessors (Dinamica's follow its sweeps) |
Watershed model (hydro.swat.run): weather and generator, HRUs (snow, ET, curve number runoff, MUSLE, percolation, lateral flow, nutrients, plant growth, scheduled management, decision tables, crop rotations, urban land), routing units, aquifers, SWAT-DEG channels, every printed table |
SWAT+ 62.0.1 built with flang 22 against the UCRT (equal to the official Windows executable in every printed digit), dumping its state each day and its printed records: the official Hubbard Brook and Ames examples, Muskingum routing, two projects made by from_rasters (one with crop rotations and urban land) and the plants' minimum C (c_factor="plant") |
identical bits (every field of every day) |
Watershed division and project files (hydro.swat.watershed, from_tables) |
QSWAT+ 3 (its sources) and the SWAT+ Editor 4.0.2 run on the same tables: a synthetic watershed and a 282 km² DEM (QSWAT+'s test data) | the Editor's 64 files, token for token |
The default solver of Omniscape and Circuitscape (conjugate gradients with an algebraic multigrid) is itself only about 2e-7 away from the exact solution; Landschaft's solves are exact, so it is compared with their exact (CHOLMOD) runs.
Checked in other ways¶
These tools follow GuidosToolbox's method, but are not compared with a run of its code:
| Tools | How they are checked |
|---|---|
| Network components and importance of MSPA cores and bridges | an independent step-by-step reproduction of GuidosToolbox's IDL routine |
| Spatial entropy | an independent reproduction of GuidosToolbox's entropy_mspainp.pro |
| Restoration status, parcellation | GuidosToolbox's values for its sample map |
| Cost distance, least-cost path, restoration corridor, contortion, simple and morphological change, FOS change, GraySpatCon metrics other than those of FOS | unit tests on constructed cases |
| Zonal statistics, extraction at points, clip, rasterize | cells against GDAL's rasterizer, statistics against NumPy and rasterstats |
| Land change: Dinamica's Expander and Patcher | Dinamica EGO 8.13's functors crash for patches of more than one cell, so their seed selection was measured with one-cell patches (acceptance by neighbours, draw by probability) and the patch growth follows the publications; statistical comparison |
Land change: Dinamica's continuous Patcher (ContinuousPatcher) and road destinations (destinations) |
Dinamica EGO 8.13's continuous Patcher crashes for patches of more than one cell, as its Patcher does: the candidates, the pruning, the quota and the draw by probability were measured with one-cell patches (exact on the cases that do not depend on the draw, statistical on 32 draws), the growth is the Patcher's; destinations compared statistically on 100 draws (Dinamica's draw errors corrected, see below) |
Hilltop protection areas (surface.hilltop) |
written from the Forest Code (Lei 12.651/2012): Dinamica EGO's CalcHilltop follows the older CONAMA 303/2002 rule on a DEM cut into elevation bands, with its own definitions (its slope criterion measures to the map's edge or to other features: 5° on an isolated cone of 21.8°), so only the heights are compared (to 0.05 m on cones); checked against an independent computation of the landforms by connected components on random DEMs, and on cones, ranges and lakes of known geometry |
| Land change: PLUS's CA with multi-type random patch seeds (CARS) | PLUS is distributed without source code or licence: written from its paper, checked by its properties (demand met, growth from patches and seeds, the threshold, the rules, the same maps with any number of threads) |
| Land change: dynamic variables, regional demands, presets, projections, scenarios, calibration | unit tests: the pieces they assemble are compared above |
Known differences
- Euclidean distance, distance to resistance, influence zones and
proximity depend on IDL's
MORPH_DISTANCE, which does not always pick the nearest object near the boundaries between the zones of different objects (about 1 % of the pixels of the test cases). Landschaft's distances are exact Euclidean distances, so these maps can differ from GuidosToolbox's in detail; influence zones and proximity are marked experimental. - MSPA Tiling reproduces GuidosToolbox's tiling exactly, including its
approximation near the tile seams when objects are larger than the
overlap. A map that fits in one tile gets the MSPA of the whole map
(GuidosToolbox drops the first
overlap / 2columns). - Zonation 5's curves (performance curves, AUC,
features_info, histograms and flexibility maps) come from the order of its second-to-last sort, while its rank map comes from the last one. Landschaft's describe the rank map it returns; they are compared with z5 evaluating that rank map (LOADmode). Flexibility maps are identical where the two orders give the same curves, and otherwise compared with a direct reproduction of Zonation's algorithm. - Zonation 5 on several threads adds up the layers per thread, and its results can differ from its run on one thread; Landschaft's do not depend on the threads and equal z5's on one thread.
- Species distributions keep the R packages' results except where
they err (each case listed in IDEAS.md and in the functions' notes).
Three of the corrections change values people compare with R: the
continuous Boyce index drops ecospat's "+1" on the last window (which
made the index depend on the scale of the predictions), country
checks use Natural Earth's
iso_a3_eh(withiso_a3, France and Norway are "-99"), and MOP's Mahalanobis distance uses the reference's covariance (mop used that of the cells tested). - Dinamica EGO's destinations (
destinations, of its road constructor): Dinamica gives each quadrantn // qrows or columns, so the lastn % qnever get a destination, and rounds its draw to an integer of the summed attraction, so the first cell of a quadrant gets half its share and a draw past the end takes the quadrant's last cell, even of no attraction. Landschaft's quadrants cover the map and its draw follows the attraction exactly. - Off Windows, the arctangent of the slopes of
surface.terrainis the C library's, as terra's is on Windows: R takes it from Windows' UCRT, whose code is not public, and other C libraries (glibc) differ from it in the last bit. The slopes then differ from R's by one unit in the last place (two in degrees). The SWAT+ port calls the C library's exp, log and powers as SWAT+ does, and carries their last bits through years of daily state: off Windows its results are within 1e-4 of each quantity's scale of SWAT+'s on Windows (2.4e-5 at most in the test cases). Everything else gives the same bits on Windows and Linux: Landschaft carries its own versions of the functions whose bits matter (log, sqrt, sin, cos, atan2, exp and powers as mingw-w64 computes them on the x87 unit, and the UCRT's hypot).
Reproduce¶
The comparisons are part of the test suite:
pip install -e ".[test]"
pytest # every test, stored references included
pytest -m live_reference # also run the originals that are installed
The live runs look for the originals in these environment variables:
| Variable | Original |
|---|---|
LANDSCHAFT_MSPA_EXE |
GuidosToolbox's mspa executable (MSPAstandalone/mspa_win64.exe) |
LANDSCHAFT_GDL_EXE |
GDL, to run GuidosToolbox's IDL code |
LANDSCHAFT_SPATCON_EXE, LANDSCHAFT_GRAYSPATCON_EXE, LANDSCHAFT_GGEO_EXE |
GuidosToolbox's helper programs |
LANDSCHAFT_RSCRIPT, LANDSCHAFT_R_LIBS |
R with landscapemetrics and Makurhini |
LANDSCHAFT_JULIA, LANDSCHAFT_JULIA_PROJECT |
Julia with Circuitscape and Omniscape |
tools/make_reference*.py regenerate the stored references from the
originals, and python tools/benchmark.py --exe <mspa executable> repeats
the MSPA timings. tools/make_reference_optimize.py and tools/benchmark_optimize.py use R
with prioritizr, prioriactions and gurobi (LANDSCHAFT_RSCRIPT,
LANDSCHAFT_R_LIBS); Marxan's references come from its executable in WSL.
tools/make_reference_prioritize.py and
tools/benchmark_prioritize.py look for Zonation 5's z5.exe and
z5_16bit.exe in LANDSCHAFT_Z5_DIR.
tools/make_reference_sdm.py and tools/benchmark_sdm.py use R with the
packages listed above in LANDSCHAFT_SDM_R_LIBS (default
%LOCALAPPDATA%/R/landschaft-sdm-lib).
tools/make_reference_simulate.py and tools/benchmark_simulate.py use R
with lulcc, OpenLand and diffeR (LANDSCHAFT_SIM_R_LIBS, default
%LOCALAPPDATA%/R/landschaft-sim-lib), CLUMondo (LANDSCHAFT_CLUMONDO,
LANDSCHAFT_CLUMONDO_FIXED), GRASS 8 with the r.futures addons in WSL,
Dinamica EGO's console (LANDSCHAFT_DINAMICA) and MOLUSCE's sources
(LANDSCHAFT_MOLUSCE, LANDSCHAFT_MOLUSCE_FIXED);
tools/benchmark_dinamica.py times the Dinamica EGO functors added in 1.46
on 4 cores.