Land change simulation¶
Land use and land cover change models: analysing the changes between maps,
estimating where change is likely, allocating future change and validating
the simulated maps: the ls.simulate module. It brings the tools of the
field together as interchangeable pieces of one model (CLUE-S, CLUMondo,
Dinamica EGO, PLUS, FUTURES), each reproducing its reference. The module is
being built phase by phase; this page grows with it.
import landschaft as ls
lu = ls.load_raster(ls.simulate.example_data("pie_landuse")) # 1985, 1991, 1999
ch = ls.simulate.changes(lu) # transitions, areas, change counts
ia = ch.intensity(gain=2, loss=1) # intensity analysis
ls.simulate.difference_components(ch.matrix()) # quantity, exchange, shift
mk = ch.markov(steps=14) # transition rates per year
ef = ls.load_raster(ls.simulate.example_data("pie_drivers"))
woe = ls.simulate.weights_of_evidence(lu[0], lu[2], ef, {"dist_built_1985": 100})
p = woe.probability(lu[2], ef) # Dinamica EGO's transition probabilities
forms = {1: ["elevation", "slope"], 2: ["elevation", "slope", "dist_built_1985"], 3: ["elevation", "slope"]}
model = ls.simulate.Model( # CLUE-S, as lulcc
potential=ls.simulate.LogisticRegression(forms).fit(lu[0], ef),
demand=ls.simulate.AreaDemand.from_maps(lu, times=[0, 6, 14]),
allocation=ls.simulate.CLUE(jitter=0.0002, scale=1e-6, max_diff=50, mean_diff=50),
rules=ls.simulate.Rules(elasticity=[0.2, 0.2, 0.2]))
sim = model.run(lu[0], ef, seed=1) # 14 yearly maps
Land systems that supply goods and services (CLUMondo), given as overlapping binary layers:
S = ls.simulate
st = S.LandSystemStack({"forest": f, "extensive": pe, "intensive": pi, "soy": soy})
st.overlaps() # the combinations (mosaics) found
roles = S.Roles(natural=["forest"], families={"cattle": ["extensive", "intensive"], "crops": ["soy"]})
model = S.Model(
potential=st.suitability({"forest": sf, "extensive": se, "intensive": si, "soy": ss}),
demand=S.ServiceDemand(demand_df), # heads of cattle, tons of soy per year
allocation=S.CLUMondo(st.supply({"extensive": {"cattle": 0.6}, "intensive": {"cattle": 2.5},
"soy": {"soy": 3.4}})),
rules=S.Rules(elasticity=0.5, roles=roles, conversion_order=["intensification", "expansion"],
caps=[S.Cap("expansion", cells=500)], restrictions=parks))
sim = model.run(st, seed=1)
sim.layers["forest"]; sim.shortfall; sim.trajectory()
Urban growth by patches (FUTURES):
demand = S.DevelopmentDemand.from_population(observed_maps, counties, observed_pop, projected_pop,
range(2020, 2051))
model = S.Model(
potential=S.DevelopmentPotential(coefficients_df), # intercept, devpressure, predictors per county
demand=demand,
allocation=S.PatchGrowing(patch_library, subregions=counties, neighborhood=10,
compactness_mean=0.4, compactness_range=0.05, incentive_power=2))
sim = model.run(developed_2020, {"slope": slope, "road_dist": road_dist}, seed=1)
sim.development # step in which each cell was developed
Change between maps¶
changes
¶
changes(
maps,
times=None,
*,
cell_area: float | None = None,
nodata=None,
labels: dict | None = None,
n_threads: int | None = None,
) -> Changes
Land change along a series of categorical maps.
The transitions between consecutive maps and from the first to the last,
the area of each class, the intensity analysis of Aldwaik & Pontius
(2012) and the number of changes per cell: the analyses of OpenLand
(contingencyTable, intensityAnalysis, acc_changes), lulcc
(crossTabulate, total) and diffeR (crosstabm,
diffTablej), with the same numbers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
maps
|
A 3-D |
required | |
times
|
The date of each map (default: the band names when they are numbers, else 0, 1, 2...). |
None
|
|
cell_area
|
float | None
|
Area of a cell in square map units (default: the product of the
resolutions; |
None
|
nodata
|
Value marking missing cells (default: the rasters' no-data value). |
None
|
|
labels
|
dict | None
|
Names of the classes, |
None
|
n_threads
|
int | None
|
Threads (default: all cores but one). |
None
|
Returns:
| Type | Description |
|---|---|
Changes
|
With |
Examples:
>>> import landschaft as ls
>>> lu = ls.load_raster(ls.simulate.example_data("pie_landuse"))
>>> ch = ls.simulate.changes(lu)
>>> ch.times
[1985, 1991, 1999]
>>> ia = ch.intensity(gain=2, loss=1) # gains of built land, losses of forest
>>> ia.interval.columns.tolist()
['Period', 'PercentChange', 'St', 'U']
Changes
dataclass
¶
Changes(
maps: list[Map],
times: list,
classes: ndarray,
cell_area: float,
labels: dict | None = None,
_pairs: dict = dict(),
)
Land change along a series of categorical maps (see changes()).
table
property
¶
Transitions between consecutive maps, one row per pair of classes (OpenLand's lulc_Multistep).
Columns Period ("t0-t1"), From, To, km2 (cells times
the cell area, over 10⁶), cells, Interval, yearFrom,
yearTo; pairs without cells are left out.
one_step
property
¶
Transitions from the first map to the last (OpenLand's lulc_Onestep).
total_area
property
¶
Area of the first period's cells: {"km2": ..., "cells": ...} (OpenLand's totalArea).
areas
property
¶
Cells of each class (columns) in each map (rows, by time), as lulcc's total.
matrix
¶
Transition matrix (cells) from the map at t0 to the map at t1 (default: first to last).
intensity
¶
intensity(gain, loss, *, area: bool = True) -> IntensityAnalysis
Intensity analysis at the interval, category and transition levels (OpenLand's intensityAnalysis).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gain
|
The class whose gains the transition level examines (OpenLand's
|
required | |
loss
|
The class whose losses the transition level examines
( |
required | |
area
|
bool
|
Areas in km² (default) or in cells. |
True
|
Returns:
| Type | Description |
|---|---|
IntensityAnalysis
|
|
change_count
¶
How many times each cell changed along the series (OpenLand's acc_changes).
Returns:
| Type | Description |
|---|---|
(map, table)
|
The count per cell (int32, -1 where any map is missing; a
DataArray on the maps' grid when they came as rasters) and a
table of |
components
¶
Gain, persistence, loss and the quantity, exchange and shift components of the change.
difference_components() of matrix() with analysis="change".
markov
¶
The Markov chain of the change from t0 to t1 (see Markov).
IntensityAnalysis
dataclass
¶
IntensityAnalysis(
interval: DataFrame,
category_gain: DataFrame,
category_loss: DataFrame,
transition_gain: DataFrame,
transition_loss: DataFrame,
category_gain_stationarity: DataFrame,
category_loss_stationarity: DataFrame,
transition_gain_stationarity: DataFrame,
transition_loss_stationarity: DataFrame,
gain: object = None,
loss: object = None,
)
Intensity analysis of land change (Aldwaik & Pontius 2012), as OpenLand computes it.
Every rate is a percentage per time unit (per year when the times are
years). Areas are in km² (area=True) or cells.
Attributes:
| Name | Type | Description |
|---|---|---|
interval |
DataFrame
|
Per period: |
category_gain, category_loss |
DataFrame
|
Per period and class: gross gain ( |
transition_gain |
DataFrame
|
Transitions to the class |
transition_loss |
DataFrame
|
Transitions from the class |
category_gain_stationarity, category_loss_stationarity, transition_gain_stationarity, |
|
|
transition_loss_stationarity |
DataFrame
|
In how many periods each class is active / dormant (category level)
or targeted / avoided (transition level), and |
crosstab
¶
crosstab(
from_map,
to_map,
*,
nodata=None,
labels: dict | None = None,
n_threads: int | None = None,
) -> DataFrame
Cells of each class of one map that hold each class of another (a transition matrix of counts).
Rows are the classes of from_map (the earlier map, or the
comparison map), columns those of to_map (the later map, or the
reference); only cells with a value in both maps count. The classes are
those found in such cells, in increasing order (the set
diffeR::crosstabm and raster::crosstab use).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
from_map
|
Categorical maps ( |
required | |
to_map
|
Categorical maps ( |
required | |
nodata
|
Value marking missing cells (default: the rasters' no-data value; NaN is always missing). |
None
|
|
labels
|
dict | None
|
Names of the classes ( |
None
|
n_threads
|
int | None
|
Threads (default: all cores but one). |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Counts (int64), index |
Examples:
Components of difference¶
difference_components
¶
difference_components(
ctmatrix, *, analysis: str = "error", digits: int | None = None
) -> DataFrame
Agreement and the components of difference of each category and overall (diffeR's diffTablej).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ctmatrix
|
A square cross-tabulation (a DataFrame from |
required | |
analysis
|
('error', 'change')
|
Names of the first three columns: |
"error"
|
digits
|
int | None
|
Round to this many decimals as R does (diffeR rounds to 0 by default; here nothing is rounded unless asked). |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per category and an |
Examples:
overall_difference
¶
The overall difference and its components (diffeR's overallDiff, overallQtyD...).
Returns:
| Type | Description |
|---|---|
dict
|
|
Examples:
difference_resolutions
¶
difference_resolutions(
comp, ref, *, factor: int = 2, percent: bool = True, nodata=None, population=None
) -> DataFrame
Quantity, exchange and shift differences at increasingly coarse resolutions (diffeR's differenceMR).
At the original resolution the cross-tabulation of the two maps; at
factor, factor²... cells per side (up to the larger side of the
map) the composite cross-tabulation (composite_matrix()). Shift
shrinks as the resolution coarsens; quantity does not change.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
comp
|
The comparison and the reference map (same grid). diffeR gives NaN at
the coarser resolutions when a map has missing cells (see
|
required | |
ref
|
The comparison and the reference map (same grid). diffeR gives NaN at
the coarser resolutions when a map has missing cells (see
|
required | |
factor
|
int
|
Growth of the block side from one resolution to the next. |
2
|
percent
|
bool
|
Differences in % of the domain (default) or as proportions. |
True
|
population
|
Population of each category (see |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
composite_matrix
¶
Cross-tabulation at a coarser resolution by the composite method (diffeR's composite).
Each block of factor x factor cells is a mix of categories; the agreement of a category in a block is the smaller of its two shares, and the rest is distributed among the pairs of categories as Pontius & Cheuk (2006) propose. Blocks with a missing cell or cut by the edge are left out.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
comp
|
The comparison and the reference map. The categories are those of either map, matched by value (diffeR counts a missing value as a category, which leaves a row and a column of NA, and names the rows in the order the values first appear). |
required | |
ref
|
The comparison and the reference map. The categories are those of either map, matched by value (diffeR counts a missing value as a category, which leaves a row and a column of NA, and names the rows in the order the values first appear). |
required | |
factor
|
int
|
Cells per side of a block. |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
Proportions of the domain (rows |
memberships
¶
The share of each category in each block of factor x factor cells (diffeR's memberships).
terra::segregate then aggregate(fun = mean): blocks with a
missing cell, and the partial blocks at the right and bottom edges, are
NaN.
Returns:
| Type | Description |
|---|---|
dict
|
|
mad
¶
Mean absolute deviation between two continuous maps and its components (diffeR's MAD).
The deviation grid1 - grid2 is split into quantity (the mean
deviation), strata (deviations that cancel within each stratum) and
element (the rest, which coarser resolutions remove); multiple
repeats it at blocks of 2, 4, 8... cells (sums of the deviations).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid1
|
Continuous maps on the same grid (NaN = missing). |
required | |
grid2
|
Continuous maps on the same grid (NaN = missing). |
required | |
strata
|
A map of strata (integers; 0 and missing are outside every stratum);
default: one stratum over the cells of |
None
|
|
multiple
|
bool
|
All resolutions (default) or only the original one. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
sample_to_population
¶
A sample's cross-tabulation scaled to the population of each row category (diffeR's sample2pop).
Each row j becomes population[j] * row / sum(row). diffeR takes
the population of the row's position instead of the row's category
(a wrong result when the population table is in another order); here the
category decides.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ctmatrix
|
DataFrame
|
Sample counts, rows and columns named by category. |
required |
population
|
The population of each row category: a two-column table (category,
size), a |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
Markov chains¶
Markov
¶
A Markov chain of land change: the transition probabilities of a period and of each time step.
single holds the probability that a cell of class i at the start of
the period is of class j at its end (every cell counted once), multi
the probabilities per time step when the period spans steps steps:
the principal steps-th root of single (Dinamica EGO's
DetermineTransitionMatrix, single-step and multi-step matrices).
Create one with from_maps() (or Changes.markov), or from a
matrix of probabilities.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
matrix
|
Transition probabilities of the period (rows sum to 1). |
required | |
classes
|
The class of each row and column. |
required | |
steps
|
int
|
Time steps in the period. |
1
|
multi
property
¶
Transition probabilities per time step (the principal root of single).
from_counts
classmethod
¶
from_counts(counts, classes, steps: int = 1) -> Markov
From a matrix of cell counts (rows: classes at the start); classes without cells at the start are dropped.
from_maps
classmethod
¶
from_maps(
t0, t1, *, steps: int = 1, nodata=None, n_threads: int | None = None
) -> Markov
rates
¶
The transition rates as Dinamica EGO lists them: off-diagonal, positive, From, To, Rate.
Negative rates (roots that are not stochastic matrices) are left out, as Dinamica does; a cell then keeps its class with the remaining probability.
project
¶
Areas of each class after each step: areas @ P repeated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
areas
|
The area (or cells) of each class now: a sequence in the order of
|
required | |
steps
|
int | None
|
Steps to project (default: the chain's |
None
|
multi
|
bool
|
Use the per-step matrix (default) or the period's. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per step (0 = the given areas), one column per class. |
Transition potential¶
WeightsOfEvidence
¶
WeightsOfEvidence(
ranges: dict | None = None,
*,
transitions=None,
increment="auto",
min_delta: float = 1.0,
max_delta: float = 100000.0,
tolerance: float = 5.0,
categorical=(),
fix_abnormal: bool = False,
possible: str = "any",
)
Weights of evidence of land use transitions, calibrated on two maps (Dinamica EGO's method).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ranges
|
dict | None
|
How each explanatory variable is cut into ranges, by name:
|
None
|
transitions
|
The transitions |
None
|
|
increment
|
Default buffer increment of continuous variables, in the variable's
units ( |
'auto'
|
|
min_delta
|
float
|
Smallest and largest range, in cells (Dinamica's defaults: 1 and 100 000). |
1.0
|
max_delta
|
float
|
Smallest and largest range, in cells (Dinamica's defaults: 1 and 100 000). |
1.0
|
tolerance
|
float
|
Tolerance angle, degrees (default 5): a smaller angle gives more, narrower ranges. |
5.0
|
categorical
|
Names of categorical variables.
|
()
|
|
fix_abnormal
|
bool
|
Give ranges with no transition, or only transitions, finite weights
( |
False
|
possible
|
('any', 'stay')
|
Cells where a transition was possible but did not happen: every
other cell of the initial class ( |
"any"
|
Examples:
>>> import landschaft as ls
>>> lu = ls.load_raster(ls.simulate.example_data("sibuyan_landuse"))
>>> drivers = ls.load_raster(ls.simulate.example_data("sibuyan_drivers"))
>>> woe = ls.simulate.WeightsOfEvidence({"dist_roads": 250, "dist_sea": 250}, transitions=[(1, 3)])
>>> woe = woe.fit(lu[0], lu[1], drivers[[0, 10]])
>>> woe.table.columns[:5].tolist()
['Transition_From', 'Transition_To', 'Variable', 'Range_Lower_Limit', 'Range_Upper_Limit']
table
property
¶
Ranges and weights, as Dinamica EGO's report: transition, variable, range limits, possible and executed transitions, weight (W+), contrast and its significance (1 = 95 %).
fit
¶
fit(t0, t1, drivers, *, nodata=None, n_threads: int | None = None) -> WeightsOfEvidence
Calibrate the ranges and weights from the map at the start (t0) and at the end (t1).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t0
|
Categorical maps of the two dates. |
required | |
t1
|
Categorical maps of the two dates. |
required | |
drivers
|
The explanatory variables on the same grid: an
|
required | |
nodata
|
Value marking missing cells in the land use maps. |
None
|
|
n_threads
|
int | None
|
Threads (default: all cores but one). |
None
|
Returns:
| Type | Description |
|---|---|
WeightsOfEvidence
|
|
correlation
¶
Association between the ranges of each pair of variables (Dinamica EGO's correlation report).
For each transition and pair of variables, the cross-tabulation of
their ranges over every cell where both have a value gives
chi-square, Cramér's V, the contingency coefficient, the joint
entropy (natural logarithms) and the joint information uncertainty
2 (H(A) + H(B) - H(A, B)) / (H(A) + H(B)) (Bonham-Carter 1994).
Values of Cramér's V or the joint uncertainty above ~0.5 suggest
dropping or merging one of the two variables.
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
probability
¶
probability(
landscape,
drivers,
*,
transitions=None,
dtype="float64",
nodata=None,
n_threads: int | None = None,
)
Transition probabilities from the weights (Dinamica EGO's CalcWOfEProbabilityMap).
With S the sum of the weights of the cell's range in every variable of
a transition, the probability of the transition i -> j at a cell of
class i is exp(S_ij) / (1 + sum_k exp(S_ik)) over the transitions
from i asked for together (1 / (1 + exp(-S)) for a single one); a
variable missing at a cell adds nothing to its sum (as in Dinamica).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
landscape
|
The land use map the probabilities are for. |
required | |
drivers
|
The explanatory variables (as in |
required | |
transitions
|
The transitions (default: all calibrated). |
None
|
|
dtype
|
('float64', 'float32', 'uint8', 'uint16')
|
float: probabilities 0-1, NaN elsewhere; integer:
|
"float64"
|
nodata
|
Value marking missing cells in the landscape. |
None
|
|
n_threads
|
int | None
|
Threads (default: all cores but one). |
None
|
Returns:
| Type | Description |
|---|---|
DataArray
|
|
weights_of_evidence
¶
weights_of_evidence(
t0, t1, drivers, ranges: dict | None = None, **kwargs
) -> WeightsOfEvidence
Calibrate weights of evidence in one call: WeightsOfEvidence(ranges, **kwargs).fit(t0, t1, drivers).
See WeightsOfEvidence for the settings.
LogisticRegression
¶
Logistic regression of the presence of each land use class on the drivers (lulcc's glmModels).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formulas
|
dict | None
|
The drivers of each class, |
None
|
maxit
|
int
|
glm.fit's iteration limit and convergence tolerance (R's defaults). |
25
|
epsilon
|
int
|
glm.fit's iteration limit and convergence tolerance (R's defaults). |
25
|
Examples:
>>> import landschaft as ls
>>> lu = ls.load_raster(ls.simulate.example_data("pie_landuse"))
>>> ef = ls.load_raster(ls.simulate.example_data("pie_drivers"))
>>> lr = ls.simulate.LogisticRegression({2: ["elevation", "slope", "dist_built_1985"]}).fit(lu[0], ef)
>>> lr.coefficients.columns.tolist()
['(Intercept)', 'elevation', 'slope', 'dist_built_1985']
coefficients
property
¶
The coefficients of each class's model (NaN for aliased drivers, R's NA).
summary
property
¶
Per class: cells used, iterations, convergence, deviance, null deviance.
fit
¶
fit(
landscape, drivers, *, cells=None, nodata=None, n_threads: int | None = None
) -> LogisticRegression
Fit a model per class: presence (1) or absence (0) of the class in the cells.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
landscape
|
The categorical map (lulcc fits on the map of the first date). |
required | |
drivers
|
The explanatory variables: a Dataset, a |
required | |
cells
|
The cells to fit on: a boolean map or flat indices (row by row);
default every cell. Cells missing in the landscape or in a
driver of the model are left out (glm's |
None
|
|
nodata
|
Value marking missing cells in the landscape. |
None
|
Returns:
| Type | Description |
|---|---|
LogisticRegression
|
|
from_coefficients
classmethod
¶
from_coefficients(coefficients) -> LogisticRegression
A model with given coefficients (CLUMondo's and CLUE-S's alloc1.reg).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coefficients
|
|
required |
Examples:
predict
¶
predict(
drivers, *, classes=None, landscape=None, nodata=None, n_threads: int | None = None
) -> DataArray
Probability of each class in every cell (predict(type = "response") of each model).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
drivers
|
The explanatory variables (they may have changed since the fit). |
required | |
classes
|
The classes to predict (default: all fitted). |
None
|
|
landscape
|
Optional map whose missing cells stay NaN. |
None
|
Returns:
| Type | Description |
|---|---|
DataArray
|
|
Estimator
¶
Estimator(
estimator,
*,
target: str = "presence",
classes=None,
sample=None,
stratify: bool = False,
balance: bool = False,
seed=None,
)
Potential of each class from a scikit-learn classifier, one model per class.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
estimator
|
A scikit-learn classifier (or any object with |
required | |
target
|
('presence', 'expansion')
|
What the models learn: |
"presence"
|
classes
|
The classes to model (default: all). |
None
|
|
sample
|
Cells to train each model on: |
None
|
|
stratify
|
bool
|
Draw |
False
|
balance
|
bool
|
Draw as many absences as presences (after |
False
|
seed
|
Seed of the draws (numpy's generator). |
None
|
Examples:
>>> import landschaft as ls
>>> from sklearn.ensemble import RandomForestClassifier
>>> lu = ls.load_raster(ls.simulate.example_data("pie_landuse"))
>>> ef = ls.load_raster(ls.simulate.example_data("pie_drivers"))
>>> est = ls.simulate.Estimator(RandomForestClassifier(n_estimators=20, random_state=0),
... target="expansion", classes=[2], sample=5000, seed=1)
>>> p = est.fit(lu[0], ef, t1=lu[2]).predict(ef)
>>> p.dims
('class', 'y', 'x')
fit
¶
fit(
landscape,
drivers,
*,
t1=None,
cells=None,
nodata=None,
n_threads: int | None = None,
) -> Estimator
Fit a model per class.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
landscape
|
The map at the start. |
required | |
drivers
|
Explanatory variables (Dataset, |
required | |
t1
|
The later map ( |
None
|
|
cells
|
Optional boolean map of the cells to train on. |
None
|
|
nodata
|
Value marking missing cells in the maps. |
None
|
predict
¶
predict(
drivers, *, classes=None, landscape=None, nodata=None, n_threads: int | None = None
) -> DataArray
Probability of each class (predict_proba of the class's model), NaN where a driver is missing.
importance
¶
Contribution of each driver to each class's model (PLUS's driver contributions).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
('impurity', 'permutation')
|
|
"impurity"
|
n_repeats
|
int
|
Shuffles per driver and their seed ( |
5
|
seed
|
int
|
Shuffles per driver and their seed ( |
5
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
A row per driver, a column per class. |
MultiCriteria
¶
Potential from weighted factors, the weights from pairwise comparisons (AHP; MOLUSCE's MCE).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
comparisons
|
The pairwise comparison matrix: a square DataFrame indexed and named
by the drivers ( |
required | |
drivers
|
The driver names, in the order of an array's rows. |
None
|
|
transition
|
|
None
|
Examples:
>>> import pandas as pd
>>> import landschaft as ls
>>> a = pd.DataFrame([[1, 3, 5], [1 / 3, 1, 2], [1 / 5, 1 / 2, 1]],
... index=["roads", "slope", "soil"], columns=["roads", "slope", "soil"])
>>> mce = ls.simulate.MultiCriteria(a)
>>> round(float(mce.weights["roads"]), 3), mce.consistency < 0.1
(0.648, True)
consistency
property
¶
Saaty's consistency ratio (acceptable below 0.1; 0 for two drivers, -1 beyond the table).
predict
¶
The potential: sum of weight x driver rescaled to 0-1 (by its minimum and maximum).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
drivers
|
The factors (Dataset, |
required | |
landscape
|
With |
None
|
Returns:
| Type | Description |
|---|---|
DataArray
|
|
RasterPotential
¶
A potential given as maps: one layer per class (or transition), as computed elsewhere.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
maps
|
A DataArray with a first dimension naming the classes ( |
required |
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> pot = ls.simulate.RasterPotential({1: np.full((3, 4), 0.2), 2: np.full((3, 4), 0.7)})
>>> pot.predict().sel({"class": 2}).values.max()
np.float64(0.7)
predict
¶
The given maps (classes selects some).
Allocation¶
Model
¶
Model(
potential,
demand,
allocation,
rules: Rules | None = None,
dynamic: dict | None = None,
)
A land change model: potential, demand, allocation and rules.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
potential
|
The suitability of each class: a fitted |
required | |
demand
|
The cells of each class at each step ( |
required | |
allocation
|
|
required | |
rules
|
Rules | None
|
|
None
|
dynamic
|
dict | None
|
|
None
|
Examples:
>>> import landschaft as ls
>>> lu = ls.load_raster(ls.simulate.example_data("pie_landuse"))
>>> ef = ls.load_raster(ls.simulate.example_data("pie_drivers"))
>>> forms = {1: ["elevation", "slope"], 2: ["elevation", "slope", "dist_built_1985"], 3: ["elevation", "slope"]}
>>> model = ls.simulate.Model(
... potential=ls.simulate.LogisticRegression(forms).fit(lu[0], ef),
... demand=ls.simulate.AreaDemand.from_maps(lu, times=[0, 6, 14]),
... allocation=ls.simulate.CLUE(jitter=0.0002, scale=1e-6, max_diff=50, mean_diff=50),
... rules=ls.simulate.Rules(elasticity=[0.2, 0.2, 0.2]))
>>> sim = model.run(lu[0], ef, seed=1)
>>> sim.maps.shape[0]
15
run
¶
run(
landscape,
drivers=None,
*,
seed: int | None = None,
nodata=None,
n_threads: int | None = None,
) -> Simulation
Simulate every step of the demand from landscape.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
landscape
|
The map at the start (step 0 of the demand). |
required | |
drivers
|
The explanatory variables the potential needs. |
None
|
|
seed
|
int | None
|
Seed of R's generator ( |
None
|
nodata
|
Value marking missing cells in the landscape. |
None
|
Returns:
| Type | Description |
|---|---|
Simulation
|
|
save
¶
Write the fitted model as a Terraflux <https://github.com/sacridini/terraflux>_ graph (JSON).
The fitted pieces are written as they are (weights of evidence,
regressions, Markov chains, demands, patch settings, rules), the data
the model runs on as GeoTIFF files in a folder next to it (<name>_data),
so that terraflux run gives the same maps as run() with the
same seed. Terraflux runs the CLUE-S, ordered, Expander/Patcher, CARS,
MOLUSCE, CLUMondo and FUTURES allocations with weights of evidence, logistic regressions or
given maps as potential, area demands or Markov chains (or a
Regional of them), lulcc's rules and the dynamic variables
distance_to() (Euclidean or the accumulated cost over a
transition) and share_of().
Other potentials (an Estimator, MultiCriteria...)
are written as their predictions on the starting map, which needs a
model without dynamic variables.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
The model file ( |
required | |
landscape
|
The map the simulation starts from (as for |
required | |
drivers
|
The explanatory variables, or the path of a raster with a band per variable (relative to the model file). |
None
|
|
seed
|
The seed (default: drawn at random and written in the model). |
None
|
|
nodata
|
Value marking missing cells in the landscape. |
None
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Examples:
>>> import landschaft as ls
>>> S = ls.simulate
>>> lu = ls.load_raster(S.example_data("sibuyan_landuse"))
>>> dr = ls.load_raster(S.example_data("sibuyan_drivers"))
>>> model = S.presets.dinamica(lu[0], lu[1], dr, steps=14, woe=250)
>>> path = model.save(tmp_path / "dinamica.json", lu[0], dr, seed=1)
>>> again, inputs = S.Model.load(path)
Simulation
dataclass
¶
Simulation(
maps: DataArray,
steps: DataFrame,
seed: int,
history: ndarray | None = None,
ages: DataArray | None = None,
changed: ndarray | None = None,
supply_table: object = None,
demand_table: object = None,
stack: object = None,
roles: object = None,
)
The maps of a simulation and what happened at each step.
Attributes:
| Name | Type | Description |
|---|---|---|
maps |
DataArray
|
|
steps |
DataFrame
|
Per step: passes of the allocation, whether it met the demand, the largest and mean difference (cells), the cells of each class. |
seed |
int
|
The seed of R's generator the simulation used. |
layers
property
¶
With a LandSystemStack: {layer: (step, y, x)} binary maps, as the input.
trajectory
¶
Cells changing at each step, by the role of their transition (Roles).
outputs
¶
Per step, the value in table of each cell's class (CLUMondo's output maps).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
A DataFrame with a row per class and a column per output (carbon, jobs...), or a Series for one output. |
required | |
initial
|
Optional maps ( |
None
|
Returns:
| Type | Description |
|---|---|
DataArray
|
|
AreaDemand
¶
Cells each class must cover at each time step (CLUE-S's demand).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
A DataFrame with a row per time step (the first is the starting map)
and a column per class, in cells; or an array with |
required | |
classes
|
The classes of an array's columns. |
None
|
Examples:
>>> import landschaft as ls
>>> d = ls.load_table(ls.simulate.example_data("sibuyan_demand"))
>>> demand = ls.simulate.AreaDemand(d[d.scenario == "demand1"].drop(columns="scenario").set_index("time")
... .rename(columns={"forest": 1, "coconut": 2, "grassland": 3, "rice": 4,
... "other": 5}))
>>> demand.steps
14
from_maps
classmethod
¶
from_maps(maps, times=None, out_times=None, *, nodata=None) -> AreaDemand
The areas of observed maps interpolated and extrapolated linearly (lulcc's approxExtrapDemand).
Each class's area is interpolated (or extrapolated from the first or
last two maps) at out_times, negative values set to 0, and each
time's areas rounded to integers that add up to the number of cells
(the largest fractions rounded up).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
maps
|
Observed maps (a band per date, or a list). |
required | |
times
|
Their times (as in |
None
|
|
out_times
|
Times of the demand (default: every integer time from the first to the last map). |
None
|
Rules
dataclass
¶
Rules(
allowed: object = None,
elasticity: object = None,
restrictions: object = None,
history: object = None,
neighbourhood: Neighbourhood | None = None,
allow_maps: dict | None = None,
roles: object = None,
transition_resistance: dict | None = None,
conversion_order: list | None = None,
caps: list | None = None,
succession: list | None = None,
_matrix: ndarray | None = None,
)
Decision rules of the allocation (lulcc's rules, elas, mask, hist, neighb).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
allowed
|
object
|
Conversion matrix, rows the current class, columns the next (a
DataFrame named by class, or an array in the model's class order):
|
None
|
elasticity
|
object
|
Added to the suitability of each cell's own class (CLUE-S's
conversion elasticity, 0 to 1; one value for all, |
None
|
restrictions
|
object
|
Map of restricted cells ( |
None
|
history
|
object
|
Map of the steps each cell has had its class (default 1 everywhere, as lulcc), updated at each step: +1 where the class stays, 0 where it changes. |
None
|
neighbourhood
|
Neighbourhood | None
|
|
None
|
allow_maps
|
dict | None
|
CLUMondo: |
None
|
Notes
With the CLUMondo allocation the conversion matrix takes
CLUMondo's codes instead: 1 allowed, 0 not; 100 + t: only
once the cell has had its system for t years; -100 - t: only
until then (the system must leave after t years when the code is
on the diagonal); 1000 + t: autonomous change (succession) after
t years, whatever the demand. elasticity is CLUMondo's
conversion resistance, history the starting ages (a map, or a
number: random ages from 1 to it, as CLUMondo draws them).
With CLUMondo, rules may also be given by the role of the transitions
(roles, a Roles):
allowedas{role: code}({"expansion": 0}; other pairs 1);transition_resistance:{role or (from, to): value}subtracted from the score of making that transition;conversion_order: roles from the first tried to the last, as a resistance of 0.1 per position (["intensification", "expansion"]: expansion 0.1);transition_resistanceoverrides;caps:Caplimits per year (Cap("expansion", area=...));succession:Succession(autonomous changes).
mask_values
¶
lulcc's mask on the cells: 0 restricted, 1 free.
Neighbourhood
dataclass
¶
Neighbourhood thresholds (lulcc's NeighbRasterStack and nb.rules).
A class may be allocated to a cell only where the mean of weights x
presence of the class over the window around the cell reaches its
threshold (cells outside the map and missing cells are left out of the
mean).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rules
|
dict
|
|
required |
weights
|
object
|
The window (odd sides), the same for every class, or |
None
|
allow
¶
1 where each class may go, NaN where not: (cells of the map, classes).
CLUE
dataclass
¶
CLUE(
jitter: float = 0.0001,
scale: float = 0.0005,
max_iter: int = 1000,
max_diff: float = 5.0,
mean_diff: float = 5.0,
)
CLUE-S allocation (Verburg et al. 2002), as lulcc's CluesModel.
Every cell takes the class of highest suitability plus an iteration
variable per class; the variables move by scale x (demand -
allocated cells) until the allocation meets the demand. Each pass
jitters the suitabilities by U(-jitter, jitter).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
jitter
|
float
|
Half-width of the random perturbation of the suitabilities. |
0.0001
|
scale
|
float
|
Step of the iteration variables per cell of difference. |
0.0005
|
max_iter
|
int
|
Passes before giving up (the allocation of the last pass is kept). |
1000
|
max_diff
|
float
|
Largest and mean absolute difference between demand and allocation (cells) accepted. lulcc averages the signed differences, which add up to 0, so its mean criterion never acts; here it is the mean of the absolute differences, as CLUE-S defines it. |
5.0
|
mean_diff
|
float
|
Largest and mean absolute difference between demand and allocation (cells) accepted. lulcc averages the signed differences, which add up to 0, so its mean criterion never acts; here it is the mean of the absolute differences, as CLUE-S defines it. |
5.0
|
Ordered
dataclass
¶
The ordered allocation of Fuchs et al. (2013) with stochastic choice, as lulcc's OrderedModel.
The classes are served one at a time in order: a class whose demand
grows takes the cells of other classes in decreasing suitability, a
class whose demand shrinks releases its cells of lowest suitability
(to the classes served after it). With stochastic, cells are drawn
by comparing their suitability with uniform numbers (R's generator), in
suitability order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order
|
list
|
The classes, in the order they are served (all of them). |
required |
stochastic
|
bool
|
Draw the cells (default) or take the most suitable ones. |
True
|
Dynamic variables and regions¶
distance_to
¶
Distance from each cell to the nearest cell of some classes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
classes
|
A class or a list of classes. |
required | |
cost
|
|
None
|
|
unit
|
('map', 'cells')
|
Euclidean distance in the map's units (square cells) or in cells. |
"map"
|
Notes
Calling it with a map gives the distance map (NaN where the map has no
value). With no cell of the classes, every cell gets the length of the
map's diagonal (Euclidean) or inf (cost).
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> land = np.array([[1, 1, 1, 3], [1, 1, 1, 1]])
>>> ls.simulate.distance_to(3)(land)
array([[3. , 2. , 1. , 0. ],
[3.16227766, 2.23606798, 1.41421356, 1. ]])
share_of
¶
Share of the cells around each cell that hold some classes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
classes
|
A class or a list of classes. |
required | |
window
|
Side of a square window of ones (odd), or the window's weights. |
3
|
Notes
The mean of weights x presence over the window, the cells without a
value left out (the centre included), as Neighbourhood.
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> land = np.array([[1, 2, 2], [1, 1, 2], [1, 1, 1]])
>>> float(ls.simulate.share_of(2)(land)[1, 1])
0.3333333333333333
gravity_of
¶
Gravitational interaction potential of the cells of some classes.
The sum over the cells k of the classes of m_k / d^exponent
(gravity(), Dinamica EGO's
CalcInteractionPotentialMap): the pull of towns or cleared land that
grows as they grow.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
classes
|
A class or a list of classes. |
required | |
exponent
|
float
|
The distance decay exponent. |
2
|
mass
|
DataArray or array_like
|
The mass of each cell (population, production...) where it holds one of the classes; default 1. |
None
|
unit
|
('cells', 'map')
|
Distances in cells (Dinamica) or in map units. |
"cells"
|
Notes
Calling it with a map gives the potential map (NaN where the map has no value; the cells without a value are no source).
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> land = np.array([[1, 1, 1, 3], [1, 1, 1, 1]])
>>> ls.simulate.gravity_of(3)(land)
array([[0.11111111, 0.25 , 1. , 1. ],
[0.1 , 0.2 , 0.5 , 1. ]])
Regional
¶
A demand per region (municipalities, biomes, basins...).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
regions
|
Map of region ids (cells without a value, or with an id not in
|
required | |
demands
|
dict
|
|
required |
Notes
The allocations meet each region's demand on its cells: CLUE
and Ordered allocate region by region (in the order of the
ids, with the one generator), CARS with a demand and an
adaptive coefficient per region and class, ExpanderPatcher
with each region's rates and its probabilities set to 0 outside the
region (Dinamica's regionalization).
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> regions = np.repeat([[1, 1, 2, 2]], 2, axis=0)
>>> t0 = np.array([[1, 1, 1, 2], [1, 2, 1, 1]])
>>> t1 = np.array([[1, 2, 1, 2], [2, 2, 2, 2]])
>>> d = ls.simulate.Regional.from_maps(regions, [t0, t1])
>>> d.at(1, [1, 2], region=1), d.at(1, [1, 2], region=2)
(array([1., 3.]), array([1., 3.]))
index
¶
Region index (0 .. n - 1, in the order of the ids) of each cell, -1 outside the regions.
at
¶
A region's area demand at a step (region=None: all regions added up).
Land systems (CLUMondo)¶
CLUMondo
dataclass
¶
CLUMondo(
supply: object,
order: object = None,
weights: object = None,
deviation: str = "percent",
mean_deviation: float = 0.5,
max_deviation: float = 1.0,
speed: float = 0.05,
max_loops: int = 20000,
kinds: dict | None = None,
neighbourhood: NeighbourhoodEffect | None = None,
preferences: dict | None = None,
patches: dict | None = None,
_cache: dict = dict(),
)
CLUMondo's demand-driven allocation of land systems.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
supply
|
object
|
|
required |
order
|
object
|
CLUMondo's |
None
|
weights
|
object
|
Weight of each service in the iteration variables ( |
None
|
deviation
|
str
|
|
'percent'
|
mean_deviation
|
float
|
Largest mean deviation over the services and largest deviation of
one service accepted. |
0.5
|
max_deviation
|
float
|
Largest mean deviation over the services and largest deviation of
one service accepted. |
0.5
|
kinds
|
dict | None
|
|
None
|
speed
|
float
|
Starting step of the iteration (0.001-0.9; CLUMondo's default 0.05). |
0.05
|
max_loops
|
int
|
Iterations per year before giving up (the simulation stops there, as CLUMondo). |
20000
|
neighbourhood
|
NeighbourhoodEffect | None
|
|
None
|
preferences
|
dict | None
|
Location-specific additions to the suitability: |
None
|
patches
|
dict | None
|
|
None
|
Notes
The conversion resistance of each system is the model's
Rules(elasticity=...); the conversion matrix uses CLUMondo's codes
(see Rules); Rules(history=n) draws the starting ages as
CLUMondo does (rand() % n + 1, the runtime's generator with the
run's seed; CLUMondo's own runs use seed 1).
ServiceDemand
¶
The demand for goods and services at each time step (CLUMondo's demand.in).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
A DataFrame with a row per time step (the first is the start) and a
column per good or service, in their own units (tons, heads,
hectares...); or an array with |
required | |
services
|
The names of an array's columns. |
None
|
Examples:
>>> import landschaft as ls
>>> d = ls.simulate.ServiceDemand({"crops": [100, 104, 108], "cattle": [50, 50, 55]})
>>> d.steps, d.services
(2, ['crops', 'cattle'])
Supply
¶
What one cell of each land system supplies of each good or service (CLUMondo's lusmatrix).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
A DataFrame with a row per land system (its value in the map) and a
column per good or service; or |
required |
Examples:
NeighbourhoodEffect
dataclass
¶
CLUMondo's neighbourhood function (neighmat.txt and alloc2.reg).
For each system i it applies to, the suitability of cells whose
current system is in from_ gets weight x (constant + sum_k b_k x
share_k), where share_k is the weighted share of system k in
the kernel around the cell (over the cells with data), recomputed every
year from the current map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
systems
|
dict
|
|
required |
LandSystemStack
¶
LandSystemStack(
layers: dict,
*,
overlap: dict | None = None,
default_overlap: str = "mosaic",
priority: list | None = None,
min_cells: int = 0,
allow_new_mosaics=False,
)
Binary layers, possibly overlapping, as land systems (bit masks).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layers
|
dict
|
|
required |
overlap
|
dict | None
|
|
None
|
default_overlap
|
str
|
The policy of the other pairs: |
'mosaic'
|
priority
|
list | None
|
The layers from the strongest (default: the order of |
None
|
min_cells
|
int
|
Combinations with fewer cells lose their weakest layers until they reach a combination with at least that many (or a single layer). |
0
|
allow_new_mosaics
|
Systems absent from the map that the simulation may create: |
False
|
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> forest = np.array([[1, 1, 0], [1, 0, 0]])
>>> pasture = np.array([[0, 1, 1], [0, 1, 1]])
>>> st = ls.simulate.LandSystemStack({"forest": forest, "pasture": pasture})
>>> st.overlaps()["cells"].tolist()
[2, 3, 1]
>>> st.names_of(3)
('forest', 'pasture')
map
property
¶
The systems (bit masks) as a map, NaN where no data: the landscape for Model.
overlaps
¶
The systems of the map (after the overlap policies): layers and cells, by bit mask.
layers
¶
Binary layers of maps of systems ((..., y, x)): {name: same shape, 1/0, NaN no data}.
supply
¶
The supply of each system from the supply of its layers (a Supply).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
per_layer
|
dict
|
|
required |
mosaic
|
|
'split'
|
suitability
¶
A potential for the systems from maps per layer (mosaics: the mean or max of their layers).
from_categorical
classmethod
¶
from_categorical(
landscape, names: dict | None = None, *, nodata=None
) -> LandSystemStack
A stack without overlap from a categorical map (each class a layer; names: {class: name}).
Roles
dataclass
¶
What each transition between land systems does to their layers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
natural
|
list
|
The natural layers (forest, native grassland...). |
required |
families
|
dict | None
|
|
None
|
Notes
A transition is an expansion when it loses a natural layer, a
regeneration when it gains one (without losing any), an
intensification when, keeping the natural layers, it moves up a
family's ladder or gains a productive layer, a deintensification
when it moves down or loses one; stable for no change, other
otherwise. set corrects single pairs.
Examples:
>>> import landschaft as ls
>>> r = ls.simulate.Roles(natural=["forest"], families={"cattle": ["extensive", "intensive"]})
>>> r.classify({"forest"}, {"extensive"}), r.classify({"extensive"}, {"intensive"})
('expansion', 'intensification')
>>> r.classify({"forest", "extensive"}, {"forest", "intensive"})
'intensification'
Cap
dataclass
¶
Cap(
role: str | None = "expansion",
cells: int | None = None,
area: float | None = None,
pairs: list | None = None,
cell_area: float | None = None,
)
At most so many cells (or so much area) a year may make a role's transitions (or given pairs).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
role
|
str | None
|
A role ( |
'expansion'
|
cells
|
int | None
|
The limit per step, in cells, or in area units (the cells' area in
the map's units, as |
None
|
area
|
int | None
|
The limit per step, in cells, or in area units (the cells' area in
the map's units, as |
None
|
pairs
|
list | None
|
|
None
|
cell_area
|
float | None
|
Area of a cell for |
None
|
Notes
When more cells would make them, those whose change scores highest over staying keep it; the others stay, and the demand iteration finds the supply elsewhere.
Succession
dataclass
¶
Autonomous change after some years, whatever the demand (CLUMondo's code 1000 + years).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
after_years
|
int
|
Years a cell must have had its system. |
required |
from_
|
object
|
The systems (values, or tuples of layer names with a stack); or |
None
|
to
|
object
|
The systems (values, or tuples of layer names with a stack); or |
None
|
role
|
str | None
|
|
None
|
Expander and Patcher (Dinamica)¶
ExpanderPatcher
dataclass
¶
ExpanderPatcher(
expansion: object = 0.5,
expander: object = (1.0, 0.0, 1.0),
patcher: object = (1.0, 0.0, 1.0),
size_unit: str = "cells",
prune_factor: float = 10.0,
window: tuple = (3, 3),
steps: int | None = None,
)
Dinamica's Expander and Patcher allocation of transitions (AllocateTransitions).
Each step, the changes of a transition are round(rate x cells of the
origin class) (the demand's rates, e.g. a Markov chain); a
share expansion goes to the Expander, the rest (and what the
Expander could not place) to the Patcher.
- Expander: a cell's probability becomes
Pwhere at least 5 cells of its 3 x 3 window hold the target class, elseP n_j / 5(Dinamica EGO 8, measured; Soares-Filho et al. 2002 given_j / 4from 4). The cells are ranked, the firstprune_factor x changeskept, drawn in proportion to that probability and each tried once: a seed with that probability. - Patcher: seeds drawn in proportion to the probability among the first
prune_factor x changescells, until its changes are made. - Each seed grows a patch of lognormal size (mean, variance) cell by
cell, each new cell drawn among the origin-class cells around the
patch in proportion to
P x isometry^k,kthe patch cells in its window: isometry 0 leaves single cells, 1 follows the probabilities, 2 makes compact patches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
expansion
|
object
|
Share of the changes made by the Expander (one value or
|
0.5
|
expander
|
object
|
|
(1.0, 0.0, 1.0)
|
patcher
|
object
|
|
(1.0, 0.0, 1.0)
|
size_unit
|
str
|
|
'cells'
|
prune_factor
|
float
|
How many cells per change are ranked for the draw (Dinamica's 10). |
10.0
|
window
|
tuple
|
The neighbour window (rows, columns), odd. |
(3, 3)
|
steps
|
int | None
|
Steps to simulate (default: the demand's |
None
|
Notes
The functors of Dinamica EGO 8.13 crash for patches of more than one
cell, so only their seed selection could be measured (Expander:
acceptance n_j / 5 per cell and the draw by probability, from
12 x 5000 trials; Patcher: the draw by probability among the pruned
cells); the patch growth follows the published descriptions.
Stock taken by patches (Dinamica's continuous Patcher)¶
ContinuousPatcher
dataclass
¶
ContinuousPatcher(
mean: float = 1.0,
variance: float = 0.0,
isometry: float = 1.0,
unit: str = "hectares",
prune_factor: float = 10.0,
window: tuple = (3, 3),
)
New patches that take a stock (biomass, carbon, timber) up to a quota.
Dinamica EGO's PatcherContinuousState: the Patcher of a continuous
state. Each patch seed is drawn among the cells with stock and
probability, the patch grows to a lognormal size, every cell of it gives
all its stock, and patches are made until the stock taken reaches the
quota.
- Candidates: the cells with stock > 0 and probability > 0 ranked from
the highest probability; the first ones kept while their summed stock
is at most
prune_factor x quota. - Seeds: drawn from those without replacement in proportion to the probability (measured in Dinamica EGO 8.13). The list is not ranked again when it runs out: the quota is then left unmet.
- Patches: lognormal size of the given mean and variance; each new cell
drawn among the cells with stock around the patch in proportion to
P x isometry^k,kthe patch cells in its window (asExpanderPatcher); a patch stops as soon as the quota is met, so the last cell may exceed it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mean
|
float
|
Mean and variance of the patch sizes, in |
1.0
|
variance
|
float
|
Mean and variance of the patch sizes, in |
1.0
|
isometry
|
float
|
0 leaves single cells, 1 follows the probabilities, 2 makes compact patches (0-2). |
1.0
|
unit
|
('hectares', 'cells', 'area')
|
Units of the patch sizes and of the stock's area: Dinamica's stock per hectare and sizes in hectares (from the map's cell area in square metres), or per cell and in cells, or per map area unit and in map area units. |
"hectares"
|
prune_factor
|
float
|
How much more stock than the quota the ranked cells hold (Dinamica's 10). |
10.0
|
window
|
tuple
|
The neighbour window of patch growth (rows, columns), odd. |
(3, 3)
|
Notes
Dinamica's functor crashes for patches of more than one cell (as its Patcher does), so the candidates, the pruning, the seed draw and the quota were measured with one-cell patches; the patch growth is the published Patcher's. Its random numbers depend on more than its seed (the same run in another folder differs), so the comparison is statistical.
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> rng = np.random.default_rng(1)
>>> biomass = np.full((20, 20), 9.0) # per cell
>>> p = rng.random((20, 20))
>>> cp = ls.simulate.ContinuousPatcher(mean=4, variance=1, isometry=1.5, unit="cells")
>>> a = cp.allocate(biomass, p, quota=100, seed=1)
>>> a.n_cells, a.remaining
(12, -8.0)
allocate
¶
allocate(
stock, probability, quota: float, *, seed: int | None = None, nodata=None
) -> StockAllocation
Take quota units of stock as new patches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stock
|
DataArray or array_like
|
The stock of each cell (per hectare, per cell or per area unit:
see |
required |
probability
|
DataArray or array_like
|
The probability of each cell (NaN or <= 0: never taken). |
required |
quota
|
float
|
The stock to take, in the stock's units times the area unit. |
required |
seed
|
int
|
Seed of the generator (xoshiro256**); default: drawn at random. |
None
|
Returns:
| Type | Description |
|---|---|
StockAllocation
|
|
run
¶
run(
stock, probability, quotas, *, regrowth=None, seed: int | None = None, nodata=None
) -> StockSimulation
Take a quota per step, the probabilities depleted from step to step.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stock
|
As in |
required | |
probability
|
As in |
required | |
quotas
|
sequence of float
|
The quota of each step. |
required |
regrowth
|
callable
|
|
None
|
seed
|
int
|
|
None
|
Returns:
| Type | Description |
|---|---|
StockSimulation
|
|
StockAllocation
dataclass
¶
StockAllocation(
stock: object,
probability: object,
taken: object,
patches: object,
remaining: float,
n_patches: int,
n_cells: int,
seed: int,
)
What allocate() took.
Attributes:
| Name | Type | Description |
|---|---|---|
stock |
DataArray or ndarray
|
The stock left (0 in the cells taken), in the stock's units. |
probability |
DataArray or ndarray
|
The probabilities with the cells taken at 0 (Dinamica's "depleted" probabilities, for the next step). |
taken |
DataArray or ndarray
|
The stock taken from each cell in the quota's units (the stock times the cell's area; 0 elsewhere). |
patches |
DataArray or ndarray
|
The number of the patch of each cell taken (1, 2, ...; 0 elsewhere). |
remaining |
float
|
The quota not met: negative when the last cell exceeded it, positive when the ranked cells ran out. |
n_patches, n_cells |
int
|
Patches and cells taken. |
seed |
int
|
|
StockSimulation
dataclass
¶
What run() did, step by step.
Attributes:
| Name | Type | Description |
|---|---|---|
stock |
DataArray
|
The stock at the start and after each step (dimension |
taken |
DataArray
|
The stock taken in each step (quota units per cell). |
steps |
DataFrame
|
Per step: |
seed |
int
|
|
Roads built by the model (Dinamica's road constructor)¶
RoadBuilder
dataclass
¶
RoadBuilder(
friction: object,
attraction: object,
quadrants: tuple = (2, 10),
distance: tuple = (1500.0, 3000.0),
spacing: float = 1000.0,
per_quadrant: int = 1,
max_attempts: int = 15,
lottery: bool = True,
diagonal: bool = True,
relative: bool = False,
mask: object = None,
)
New roads each step, from the network towards attractive places.
Dinamica's road constructor (ClassicRoadConstructor): each step,
the attraction is kept where the distance to the roads is within
distance (and mask allows); destinations are drawn in quadrants
(destinations()); each is joined to the network down the cost of
reaching it over the friction (friction_cost(),
pathways(), ties at random with lottery), and the new roads
are merged into the network.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
friction
|
DataArray or array_like
|
The effort to build across each cell (> 0; NaN: never). |
required |
attraction
|
(DataArray, array_like or callable)
|
How attractive each cell is as a road's end, or a function of the simulated map giving it (e.g. a potential of deforestation). |
required |
quadrants
|
(int, int)
|
Horizontal and vertical divisions of the map (Dinamica's default). |
(2, 10)
|
distance
|
(float, float)
|
Range of distances from the roads (map units) where new roads may end. |
(1500, 3000)
|
spacing
|
float
|
Minimum distance between destinations of a step. |
1000
|
per_quadrant
|
int
|
As in |
1
|
max_attempts
|
int
|
As in |
1
|
lottery
|
bool
|
|
True
|
diagonal
|
bool
|
As in |
True
|
relative
|
bool
|
As in |
True
|
mask
|
(DataArray, array_like or callable)
|
Where roads may end (true / non-zero), or a function of the simulated map giving it. |
None
|
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> rng = np.random.default_rng(0)
>>> net = np.zeros((40, 40)); net[0, :] = 1
>>> rb = ls.simulate.RoadBuilder(np.ones((40, 40)), rng.random((40, 40)), quadrants=(2, 2),
... distance=(5, 20), spacing=5)
>>> new = rb.step(net, seed=1)
>>> int(np.nansum(new) > np.nansum(net))
1
step
¶
One step of building: the network with the new roads.
landscape feeds attraction and mask when they are
functions of the map.
run
¶
steps steps of building: the network at the start and after each step.
dynamic
¶
dynamic(network, *, seed: int | None = None) -> RoadDistance
The distance to the roads as a dynamic variable of a Model that builds roads each step.
RoadDistance
¶
RoadDistance(builder: RoadBuilder, network, seed: int)
Distance to a growing road network: a dynamic variable (see dynamic()).
Called with a map it gives the distance (map units) to the roads; called
with a different map than the last time (the next step of a
simulation), it first builds a step of roads, with the map feeding the
builder's attraction and mask. networks keeps the network of each
step; reset() goes back to the first.
friction_cost
¶
friction_cost(
sources,
friction,
*,
diagonal: bool = True,
relative: bool = False,
directions: bool = False,
nodata=None,
)
Cost of reaching every cell from the nearest source over a friction surface.
A step into a cell costs its friction, times sqrt(2) on a diagonal
(diagonal), 8 neighbours; every cell gets the cost of its cheapest
path from a source (Dinamica EGO's CalcCostMap; Dijkstra's
algorithm, the same sums as Dinamica's push-broom run to convergence).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sources
|
DataArray or array_like
|
Cells with a value other than 0 are sources (cost 0). |
required |
friction
|
DataArray or array_like
|
The effort to cross each cell (> 0; NaN: impassable). |
required |
diagonal
|
bool
|
Diagonal steps cost sqrt(2) times more. |
True
|
relative
|
bool
|
Frictions per map unit (metres): multiplied by the cell size (needs square cells). |
False
|
directions
|
bool
|
Also give the direction of each cell's predecessor on its cheapest path (Dinamica's codes 1 NW, 2 N, 3 NE, 4 E, 5 SE, 6 S, 7 SW, 8 W; 0 at the sources). |
False
|
nodata
|
Missing value of |
None
|
Returns:
| Type | Description |
|---|---|
xarray.DataArray or numpy.ndarray, or a tuple (cost, directions)
|
NaN where the friction is missing or no source is reached (directions: 255). |
Notes
Ties between predecessors go to the first neighbour in row order (NW, N, NE, W, E, SW, S, SE); Dinamica's follow the order of its sweeps.
Examples:
destinations
¶
destinations(
attraction,
quadrants=(1, 1),
*,
per_quadrant: int = 1,
min_distance: float = 0.0,
max_attempts: int = 15,
seed: int | None = None,
nodata=None,
)
Cells drawn by attraction in quadrants of a map.
The map is cut into quadrants = (horizontal, vertical) parts (the
columns into horizontal, the rows into vertical); in each, up to
per_quadrant cells are drawn in proportion to their attraction
(cells of attraction <= 0 or missing never); a draw closer than
min_distance (map units) to an earlier destination is drawn again,
up to max_attempts times. Dinamica EGO's CalcDestinationMap.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
attraction
|
DataArray or array_like
|
|
required |
quadrants
|
(int, int)
|
Horizontal (column) and vertical (row) divisions. |
(1, 1)
|
per_quadrant
|
int
|
|
1
|
min_distance
|
float
|
In map units (cells without a georeference). |
0.0
|
max_attempts
|
int
|
|
15
|
seed
|
int
|
|
None
|
Returns:
| Type | Description |
|---|---|
DataArray or ndarray
|
The attraction of the destinations, NaN elsewhere. |
Notes
Dinamica EGO 8.13 gives each quadrant n // q rows or columns (the
last n % q never get a destination) and draws an integer of the
summed attraction with rounding (the first cell of a quadrant gets half
its share, and a draw past the end takes the quadrant's last cell even
with no attraction, about 1 / (2 x summed attraction) of the time).
Here the quadrants cover the map (floor(k n / q) boundaries) and
the draw is in exact proportion to the attraction.
Examples:
pathways
¶
pathways(
network,
sources,
cost=None,
*,
directions=None,
lottery: bool = False,
merge: bool = True,
value: float = 1,
seed: int | None = None,
nodata=None,
)
Paths from source cells to a network down a cost surface.
From each source (in row order) a path goes to the cheapest of its 8
neighbours until a cell of cost <= 0 (the network, e.g. the sources of
friction_cost()); or, given directions, follows them until a
cell of the network. Dinamica EGO's CalcPathwayMap.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
network
|
DataArray or array_like
|
The network (cells with a value other than 0 or missing). |
required |
sources
|
DataArray or array_like
|
Where paths start (cells with a value other than 0), e.g.
|
required |
cost
|
DataArray or array_like
|
The cost of reaching the network ( |
None
|
directions
|
DataArray or array_like
|
Instead of |
None
|
lottery
|
bool
|
Ties between cheapest neighbours drawn at random (else the first in row order: NW, N, NE, W, E, SW, S, SE). |
False
|
merge
|
bool
|
The network with the new paths; else only the new paths. |
True
|
value
|
float
|
The value of the new path cells. |
1
|
seed
|
int
|
For the lottery. |
None
|
Returns:
| Type | Description |
|---|---|
DataArray or ndarray
|
NaN off the network and the paths. |
Notes
A source of missing cost is skipped; a local minimum of the costs (no cheaper neighbour before the network) is an error, as in Dinamica.
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> net = np.zeros((3, 5)); net[:, 0] = 1
>>> src = np.zeros((3, 5)); src[1, 4] = 1
>>> cost = ls.simulate.friction_cost(net, np.ones((3, 5)))
>>> np.nan_to_num(ls.simulate.pathways(net, src, cost, value=2))
array([[1., 2., 2., 2., 0.],
[1., 0., 0., 0., 2.],
[1., 0., 0., 0., 0.]])
Multi-type random patch seeds (PLUS)¶
CARS
dataclass
¶
CARS(
weights: object = 1.0,
seeds: object = 0.1,
decay: float = 0.9,
step_size: float = 500.0,
window: int = 3,
max_iter: int = 5000,
steps: int | None = None,
)
PLUS's CA based on multi-type random patch seeds (Liang et al. 2021).
Each step, CARS iterates from the current map until every class has the
cells the demand asks for. In an iteration, each free cell i gives
each class k the overall probability
OP = P(i,k) x Omega(i,k) x D(k),Omega = w(k) xthe cells ofkin thewindow x windowneighbourhood (the centre left out) overwindow^2 - 1;- where
Omega = 0, a random seed:OP = P x (r x mu(k)) x Dwhen a uniformr < P, else 0;
a roulette wheel over OP picks a class c, and the cell turns to
c when P(i,c) > tau and the conversion is allowed;
tau = decay^l x r, r normal of mean 1 in [0, 2], l raised by
one after each iteration that cut the total gap to the demand by less
than step_size cells. D(k) starts at 1 and grows while the
class's shortage grows (shrinks while its excess grows).
The growth probabilities P come from the model's potential (one
layer per class, as an Estimator with target="expansion"
gives: PLUS's LEAS); the demand is an AreaDemand or a
Markov chain projected from the starting map (PLUS's default);
the rules give the conversions allowed (allowed, 0/1, rows the
current class) and the restricted cells (restrictions).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
object
|
Neighbourhood weight |
1.0
|
seeds
|
object
|
Patch generation threshold |
0.1
|
decay
|
float
|
Decay factor |
0.9
|
step_size
|
float
|
Gap reduction (cells per iteration) below which the threshold decays (PLUS's "Step"; the paper's 500). |
500.0
|
window
|
int
|
Side of the neighbourhood, odd (3 = the Moore neighbourhood). |
3
|
max_iter
|
int
|
Iterations per step before giving up (the map is kept as it is). |
5000
|
steps
|
int | None
|
Steps to simulate (default: the demand's). |
None
|
Notes
From the paper alone (PLUS has no source code): the standard deviation
of the threshold's normal r is not given (here 1/3, values outside
[0, 2] drawn again); the changes proposed in an iteration are made in a
random order, a class taking no cell once it has its demand and losing
none while it has no more than its demand (the paper does not say how a
class is held at its demand). The random numbers of a cell come from a
hash of (seed, step, iteration, cell): the same seed gives the same
simulation with any number of threads.
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> S = ls.simulate
>>> land = np.ones((40, 40)); land[15:25, 15:25] = 2
>>> p = S.RasterPotential({1: np.full((40, 40), 0.1), 2: np.full((40, 40), 0.8)})
>>> demand = S.AreaDemand(np.array([[1500, 100], [1400, 200]]), classes=[1, 2])
>>> sim = S.Model(p, demand, S.CARS()).run(land, seed=1)
>>> int((sim.maps[-1] == 2).sum())
200
MOLUSCE¶
MolusceWoE
¶
MOLUSCE's weights of evidence (WoeManager): one model per transition, persistence included.
Each factor is cut into categories (bins: (-inf, b0) -> 1,
[b0, b1) -> 2... [bn, inf) -> n + 2; or its integer values
themselves); for each transition code observed between the two maps
(the cells that went from i to j, i == j included), every
category gets W+ + sum(W-) - W- over the factor's categories (halved
with two categories), and a cell's total weight is the sum over the
factors. A cell of class i is predicted to take the transition
i -> j of largest total weight; its confidence is 100 x
(sigmoid(largest) - sigmoid(second largest)), truncated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bins
|
dict | None
|
|
None
|
unit_cell
|
float
|
MOLUSCE's unit cell (areas in unit cells). |
1
|
Notes
MOLUSCE keeps the largest weight seen before the largest as the
second, which depends on the order of the classes: here the second
largest weight (tested against MOLUSCE with tools/molusce_fixed.patch).
MOLUSCE also stores the factors as 8-bit integers, wrapping integer
categories outside 0-255 and truncating fractions; here integer
categories keep their values and fractional values need bins.
fit
¶
fit(t0, t1, drivers, *, nodata=None) -> MolusceWoE
Fit the weights on the change between two maps.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t0
|
The maps before and after (the same classes). |
required | |
t1
|
The maps before and after (the same classes). |
required | |
drivers
|
The factors (a Dataset, |
required |
weights_table
¶
The weight of each category of each driver, per transition.
total
¶
Total weight map of each transition code (NaN where a factor or the maps had none).
Without drivers, the maps of the fit (MOLUSCE's); with them, the
weights applied to these drivers (dynamic variables).
potentials
¶
MOLUSCE's transition potentials: sigmoid(total weight) x 100 as 8-bit integers, per (from, to).
prediction
¶
The transition each cell is predicted to take and its confidence (0-100).
Returns:
| Type | Description |
|---|---|
(ndarray, ndarray)
|
The predicted class (NaN where the cell has none) and the confidence (NaN outside the cells with a class of the model). |
predict
¶
Total weight per transition as probability_i_to_j layers (sigmoid), for the other allocations.
MolusceCA
dataclass
¶
MOLUSCE's simulator: each iteration makes the observed transitions where the model is most confident.
At each iteration the potential predicts every cell's transition and
its confidence (MolusceWoE, or any potential with a layer per
class: the class of largest potential, confidence 100 x the gap to
the second); then, for each pair of classes in order, the n cells
(the demand: the cross table of the observed change, the same every
iteration) predicted to take that transition with the highest
confidence plus U(0, 1) / 1000 change.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
iterations
|
int
|
Iterations (steps) to simulate. |
1
|
Notes
The random numbers are numpy's legacy generator (RandomState(seed)),
as MOLUSCE draws them after numpy.random.seed(seed). Among cells of
equal confidence and draw (which the 1/1000 rarely leaves), the later
cells change first (MOLUSCE leaves the order to numpy's quicksort).
Urban growth (FUTURES)¶
PatchGrowing
dataclass
¶
PatchGrowing(
patch_sizes: object,
subregions: object = None,
potential_subregions: object = None,
pressure: object = None,
neighborhood: int = 10,
approach: str = "gravity",
gamma: float = 1.5,
scaling_factor: float = 1.0,
discount_factor: float = 1.0,
compactness_mean: float = 0.4,
compactness_range: float = 0.05,
num_neighbors: int = 4,
seed_search: str = "probability",
incentive_power: float = 1.0,
weight: object = None,
zoning: object = None,
zoning_effects: object = None,
climate: object = None,
)
FUTURES' patch growing allocation (r.futures.simulation).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
patch_sizes
|
object
|
The library of patch sizes (cells): a list for every subregion, or
|
required |
subregions
|
object
|
The subregions map (default: one subregion, id 1). |
None
|
potential_subregions
|
object
|
Subregions of the potential coefficients, when they differ. |
None
|
pressure
|
object
|
The starting development pressure (default: computed from the
landscape with |
None
|
neighborhood
|
int
|
The development pressure added around every new cell (the module's
|
10
|
approach
|
int
|
The development pressure added around every new cell (the module's
|
10
|
gamma
|
int
|
The development pressure added around every new cell (the module's
|
10
|
scaling_factor
|
int
|
The development pressure added around every new cell (the module's
|
10
|
discount_factor
|
float
|
Multiplies the patch sizes of the library. |
1.0
|
compactness_mean
|
float
|
The distance exponent of the candidate ranking, drawn uniformly in mean +- range / 2 for every candidate. |
0.4
|
compactness_range
|
float
|
The distance exponent of the candidate ranking, drawn uniformly in mean +- range / 2 for every candidate. |
0.4
|
num_neighbors
|
int
|
4 or 8: the cells a patch grows to. |
4
|
seed_search
|
str
|
|
'probability'
|
incentive_power
|
float
|
Infill (> 1) or sprawl (< 1): the probability raised to this power (on a lookup of 1001 values, as the module). |
1.0
|
weight
|
object
|
Map of -1 to 1 changing the probability ( |
None
|
zoning
|
object
|
Map of zone ids and their effects: |
None
|
zoning_effects
|
object
|
Map of zone ids and their effects: |
None
|
climate
|
object
|
|
None
|
DevelopmentDemand
¶
Cells each subregion develops at each step (r.futures.simulation's demand file).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
A DataFrame with a row per simulated step (indexed by year) and a
column per subregion (its value in the subregions map); or an array
with |
required | |
subregions
|
The subregion of each column of an array. |
None
|
|
population
|
The population each subregion adds per step, as |
None
|
Examples:
>>> import landschaft as ls
>>> d = ls.simulate.DevelopmentDemand({1: [10, 12], 2: [5, 0]})
>>> d.steps, d.subregions
(2, [1, 2])
from_population
classmethod
¶
from_population(
developed,
subregions,
observed_population,
projected_population,
simulation_times,
*,
methods=("linear", "logarithmic"),
nodata=None,
) -> DevelopmentDemand
The demand from the relation of developed cells to population (r.futures.demand).
For each subregion, the developed cells of the observed maps are
regressed on the observed population ("linear",
"logarithmic", "exponential", "logarithmic2",
"exp_approach"; the smallest RMSE wins; a decreasing fit falls
back to the latest cells per person), the population is interpolated
linearly at simulation_times and the demand is the yearly
difference of the predicted cells, truncated to whole cells.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
developed
|
The observed maps of developed cells (1 developed), one per observed time (a list or a DataArray with a band per time). |
required | |
subregions
|
The subregions map. |
required | |
observed_population
|
DataFrames with the time as first column (or index name) and a column per subregion. |
required | |
projected_population
|
DataFrames with the time as first column (or index name) and a column per subregion. |
required | |
simulation_times
|
The times of the simulation, the first being the start. |
required | |
methods
|
The candidate regressions. |
('linear', 'logarithmic')
|
Returns:
| Type | Description |
|---|---|
DevelopmentDemand
|
With |
DevelopmentPotential
¶
The development potential's coefficients per subregion (r.futures.potential's table).
p = 1 / (1 + exp(-(intercept + b_pressure * pressure + sum(b_k * x_k))))
with the coefficients of the cell's (potential) subregion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
table
|
A DataFrame indexed by subregion with the columns |
required |
Examples:
>>> import landschaft as ls
>>> pot = ls.simulate.DevelopmentPotential({"intercept": -2.0, "devpressure": 0.5, "slope": -0.1})
>>> pot.predictors
['slope']
fit
classmethod
¶
fit(
samples: DataFrame,
*,
response: str = "developed",
pressure: str = "devpressure",
predictors=None,
subregions: str | None = "subregion",
random: str | None = None,
dredge: bool = False,
min_variables: int = 1,
max_variables: int | None = None,
fixed=None,
) -> DevelopmentPotential
Fit the potential on sampled cells (r.futures.potential).
A binomial mixed model of new development: response ~ predictors +
(1 | subregion) (R's glmer, Laplace), or with a random slope
(random | subregion); with one subregion, R's glm (bit for
bit). The coefficients per subregion are the fixed effects plus the
subregion's random effects (lme4's coef).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
samples
|
DataFrame
|
One row per sampled cell (rows with missing values are dropped). |
required |
response
|
str
|
The 0/1 column of new development. |
'developed'
|
pressure
|
str
|
The development pressure column (the |
'devpressure'
|
predictors
|
The predictor columns (default: all other columns). |
None
|
|
subregions
|
str | None
|
The subregion column ( |
'subregion'
|
random
|
str | None
|
Column with a random slope per subregion (usually the pressure, which then has no fixed effect, as r.futures.potential). |
None
|
dredge
|
bool
|
Choose the predictors by AIC over every subset (MuMIn's dredge)
with |
False
|
Returns:
| Type | Description |
|---|---|
DevelopmentPotential
|
With |
surface
¶
The development probability (r.futures.potsurface): 1 / (1 + exp(-(intercept + b x ...))) in double.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
drivers
|
The predictors (named as the table's columns). |
required | |
pressure
|
The development pressure map. |
required | |
subregions
|
The subregions map (cells of subregions without coefficients are NaN); default: the only row. |
None
|
development_pressure
¶
development_pressure(
developed,
size: int,
*,
method: str = "gravity",
gamma: float | None = 1.5,
scaling_factor: float | None = 1.0,
pad: bool = True,
nodata=None,
) -> DataArray
The development pressure of each cell (r.futures.devpressure).
The sum over the developed cells within size cells (circular window,
the cell itself left out) of 1 ("occurrence"), scaling_factor /
d^gamma ("gravity") or scaling_factor * exp(-2 d / gamma)
("kernel"), as GRASS's r.mfilter computes it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
developed
|
1 developed, 0 not, NaN excluded. |
required | |
size
|
int
|
Radius of the window, in cells. |
required |
method
|
str
|
The decay of the pressure with distance. |
'gravity'
|
gamma
|
str
|
The decay of the pressure with distance. |
'gravity'
|
scaling_factor
|
str
|
The decay of the pressure with distance. |
'gravity'
|
pad
|
bool
|
The module's |
True
|
Returns:
| Type | Description |
|---|---|
DataArray
|
float32, NaN where excluded. |
Examples:
Flooding
dataclass
¶
Flooding(
catchments: object,
adaptive_capacity: object,
damage: object,
response: tuple,
response_stddev: float = 0.0,
depths: dict | None = None,
probabilities: dict | None = None,
hand: object = None,
hand_percentile: float | None = None,
damage_regions: object = None,
adaptation: object = None,
redistribution: object = None,
)
FUTURES' flood submodel (Sanchez et al. 2023): floods, damage, response, abandonment, migration.
After the development of each step, a flood may strike each catchment:
with probability max (the largest flood probability of the
catchment's cells, or of the depth maps), at a probability p drawn
uniformly. Its depth at each cell comes from the depth maps of the
probabilities (the map of the largest probability below p) or, from
flood probability maps, as the HAND percentile of the cells flooded at
p minus the cell's HAND. The damage is the depth-damage curve's (%)
unless the cell was adapted to such floods; a damaged developed cell
then retreats (abandoned, its demand moved to a subregion drawn from the
redistribution matrix, scaled by the population densities), adapts
(protected up to the return period of the flood) or stays, from its
adaptive capacity and the response lines, perturbed by a Gaussian.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
catchments
|
object
|
Map of catchments (HUCs): floods are drawn per catchment. |
required |
adaptive_capacity
|
object
|
Map from -1 (most vulnerable) to 1 (most resilient). |
required |
damage
|
object
|
Depth-damage curves: a DataFrame with the depth levels as columns and the damage (%) per damage region in rows (one row: the same curve everywhere). |
required |
response
|
tuple
|
|
required |
response_stddev
|
float
|
Standard deviation of the Gaussian added to capacity and damage. |
0.0
|
depths
|
dict | None
|
|
None
|
probabilities
|
dict | None
|
Instead of depths: |
None
|
hand
|
dict | None
|
Instead of depths: |
None
|
hand_percentile
|
dict | None
|
Instead of depths: |
None
|
damage_regions
|
object
|
Which curve each cell uses: |
None
|
adaptation
|
object
|
Map of the return period each cell is already protected against. |
None
|
redistribution
|
object
|
DataFrame with a row per subregion people leave and a column per destination: the probabilities (%). Without it, retreating cells are abandoned and their demand leaves the area. |
None
|
patch_library
¶
patch_library(
start,
end,
*,
subregions=None,
per_subregion: bool = False,
threshold: float = 0.0,
nodata=None,
)
The sizes of the patches developed between two maps (r.futures.calib's patch library).
Patches are the 4-connected groups of cells developed in end and not
in start (inside a subregion); their sizes, in cells, are what
PatchGrowing draws from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
Developed (1) maps at two dates. |
required | |
end
|
Developed (1) maps at two dates. |
required | |
subregions
|
Map of subregions (cells outside, or in subregion 0, are ignored). |
None
|
|
per_subregion
|
bool
|
A library per subregion ( |
False
|
threshold
|
float
|
Smallest patch area kept (in the map's area units). |
0.0
|
Returns:
| Type | Description |
|---|---|
list or dict
|
The sizes, sorted. |
Examples:
calibrate_patches
¶
calibrate_patches(
model,
start,
end,
drivers=None,
*,
compactness_means=(0.4,),
compactness_ranges=(0.05,),
discount_factors=(1.0,),
repeat: int = 10,
seed: int = 1,
threshold: float = 0.0,
per_subregion: bool = False,
) -> DataFrame
Calibrate FUTURES' patch compactness and discount factor (r.futures.calib).
The patches observed between start and end give the patch
library and the histograms of patch area and compactness
(perimeter / (2 sqrt(pi area))); each combination of the parameters
is simulated repeat times from start (the model's demand and
potential) and its patches compared with the observed ones by the
histograms' chi-square distance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
A |
required | |
start
|
The observed maps of development. |
required | |
end
|
The observed maps of development. |
required | |
drivers
|
The predictors of the potential. |
None
|
|
compactness_means
|
The values to try (every combination). |
(0.4,)
|
|
compactness_ranges
|
The values to try (every combination). |
(0.4,)
|
|
discount_factors
|
The values to try (every combination). |
(0.4,)
|
|
repeat
|
int
|
Simulations per combination (seeds |
10
|
threshold
|
float
|
Smallest patch area kept. |
0.0
|
per_subregion
|
bool
|
A patch library per subregion. |
False
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Per combination: discount factor, compactness mean and range, the
area and compactness errors (normalized by their largest) and their
mean, from the best; |
Validation¶
three_map_comparison
¶
three_map_comparison(
t0,
t1,
simulated,
factors=(1,),
*,
categories=None,
nodata=None,
n_threads: int | None = None,
) -> ThreeMapComparison
Compare the reference maps at the start and the end with the simulated end at several resolutions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t0
|
The reference maps at the start and at the end. |
required | |
t1
|
The reference maps at the start and at the end. |
required | |
simulated
|
The simulated map of the end. |
required | |
factors
|
Block sides (cells) of the resolutions (1: the cells themselves). |
(1,)
|
|
categories
|
The classes (default: those found in the three maps). |
None
|
Returns:
| Type | Description |
|---|---|
ThreeMapComparison
|
With |
Notes
lulcc takes the cells with a value in t0 and, where t1 or the
simulation lack one there, misaligns their blocks; here the cells
without a value in any of the three maps are left out of all three.
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> t0 = np.array([[1, 1, 1, 1], [1, 1, 2, 2]])
>>> t1 = np.array([[2, 2, 1, 1], [1, 1, 2, 2]])
>>> sim = np.array([[2, 1, 2, 1], [1, 1, 2, 2]])
>>> tmc = ls.simulate.three_map_comparison(t0, t1, sim, factors=[1, 2])
>>> tmc.figure_of_merit()["overall"].round(3).tolist()
[0.333, 0.333]
ThreeMapComparison
¶
Three maps compared at several resolutions (lulcc's ThreeMapComparison).
Attributes:
| Name | Type | Description |
|---|---|---|
factors |
list of int
|
The resolutions (block sides, in cells). |
categories |
list
|
The classes. |
tables |
dict
|
|
table
¶
The three-dimensional table as a DataFrame: rows (reference t0, reference t1), columns simulated.
agreement_budget
¶
Sources of agreement and disagreement (lulcc's AgreementBudget), as shares of the area.
misses (change simulated as persistence), hits,
wrong_hits (change simulated as change to another class),
false_alarms (persistence simulated as change) and
correct_rejections (persistence simulated correctly; not for a
transition): a row per level (overall, a class, a transition)
and resolution.
figure_of_merit
¶
The figure of merit (Pontius et al. 2011, equations 9-11) at each resolution.
Returns:
| Type | Description |
|---|---|
dict
|
|
compare_maps
¶
MOLUSCE's comparison of two maps (DependenceCoef): kappa, agreement and dependence.
Returns:
| Type | Description |
|---|---|
dict
|
|
Examples:
error_budget
¶
error_budget(
reference, simulated, *, iterations: int = 3, scale: int = 2, nodata=None
) -> DataFrame
MOLUSCE's error budget (Pontius 2002): agreement for levels of information, at coarser resolutions.
The share of agreement between the maps when the simulation is given
no / medium / perfect information of quantity and of location
(no_no, no_medium, medium_medium, medium_perfect,
perfect_perfect), at the map's resolution and iterations - 1
times coarser, each time merging scale x scale cells (the edges
left over dropped, as MOLUSCE does).
Returns:
| Type | Description |
|---|---|
DataFrame
|
A row per resolution ( |
Examples:
futures_validation
¶
FUTURES' validation (r.futures.validation): disagreement, kappa, kappa simulation, change metrics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reference
|
The observed and the simulated maps (integer classes; FUTURES uses 0 undeveloped, 1 developed). |
required | |
simulated
|
The observed and the simulated maps (integer classes; FUTURES uses 0 undeveloped, 1 developed). |
required | |
original
|
The map at the start: adds kappa simulation (van Vliet et al. 2011) and, for 0/1 maps, the change metrics of Pontius et al. (2008). |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Examples:
futures_validation_grid
¶
futures_validation_grid(
reference,
simulated,
original=None,
*,
size: int | None = None,
regions=None,
nodata=None,
n_threads: int | None = None,
) -> DataFrame
FUTURES' validation per tile or per region (r.futures.gridvalidation).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reference
|
As |
required | |
simulated
|
As |
required | |
original
|
As |
required | |
size
|
int | None
|
Tiles of |
None
|
regions
|
Or a map of region ids (one row per region). |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
A row per tile ( |
fuzzy_similarity
¶
fuzzy_similarity(
first,
second,
*,
window: int = 5,
decay: str = "exponential",
nodata=None,
n_threads: int | None = None,
) -> FuzzySimilarity
Fuzzy similarity of two categorical maps (Dinamica EGO's CalcReciprocalSimilarityMap).
At each cell with a class in first: the membership of that class
in second around the cell (2^(-d/2) for the nearest cell of the
class within the window x window square, d in cells; or 1 with
decay="constant"; 0 if none), the smaller of it and the reverse
membership where second has a class too; and the same for the
cells of second.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
first
|
Categorical maps (cells without a value take no part). |
required | |
second
|
Categorical maps (cells without a value take no part). |
required | |
window
|
int
|
Side of the square window, odd (1: only the cell itself). |
5
|
decay
|
('exponential', 'constant')
|
|
"exponential"
|
Returns:
| Type | Description |
|---|---|
FuzzySimilarity
|
|
Examples:
FuzzySimilarity
dataclass
¶
FuzzySimilarity(
first: object,
second: object,
first_mean: float,
second_mean: float,
window: int,
decay: str,
)
The fuzzy similarity of two maps (fuzzy_similarity()).
Attributes:
| Name | Type | Description |
|---|---|---|
first, second |
The similarity at each cell with a class in the first (second) map (NaN elsewhere). |
|
first_mean, second_mean |
Their means (Dinamica's "first" and "second" similarity). |
|
window, decay |
The settings. |
differences
¶
The map of differences: each cell that changed between the maps, in its new class; NaN elsewhere.
similarity_of_differences
¶
similarity_of_differences(
t0,
observed,
simulated,
*,
windows=(1, 3, 5, 7, 9, 11),
decay="exponential",
nodata=None,
n_threads: int | None = None,
) -> DataFrame
Dinamica EGO's validation: fuzzy similarity of the observed and the simulated change, per window.
The maps of differences() of the observed and the simulated end
are compared with fuzzy_similarity().
Returns:
| Type | Description |
|---|---|
DataFrame
|
A row per window: |
Examples:
validate
¶
validate(
t0,
observed,
simulated,
*,
factors=(1, 2, 4, 8, 16),
windows=(1, 3, 5, 7, 9, 11),
decay: str = "exponential",
budget_iterations: int = 4,
metrics=None,
level: str = "class",
nodata=None,
n_threads: int | None = None,
) -> Validation
Compare a simulated map with the observed one in every way the land change tools do.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t0
|
The map at the start of the simulated period. |
required | |
observed
|
The observed and the simulated end. |
required | |
simulated
|
The observed and the simulated end. |
required | |
factors
|
Resolutions of the three-map comparison (block sides, cells). |
(1, 2, 4, 8, 16)
|
|
windows
|
Windows of the fuzzy similarity of the differences. |
(1, 3, 5, 7, 9, 11)
|
|
decay
|
Windows of the fuzzy similarity of the differences. |
(1, 3, 5, 7, 9, 11)
|
|
budget_iterations
|
int
|
Resolutions of MOLUSCE's error budget (each twice the last). |
4
|
metrics
|
Landscape metrics ( |
None
|
Returns:
| Type | Description |
|---|---|
Validation
|
|
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> rng = np.random.default_rng(0)
>>> t0 = rng.integers(1, 4, (40, 40)).astype(float)
>>> obs = t0.copy(); obs[5:15, 5:15] = 3
>>> sim = t0.copy(); sim[7:17, 6:16] = 3
>>> v = ls.simulate.validate(t0, obs, sim, factors=[1, 2, 4], windows=[1, 3, 5])
>>> bool(v.summary()["similarity_5"] > v.summary()["similarity_1"])
True
Validation
dataclass
¶
Validation(
three_map: object,
similarity: DataFrame,
kappa: dict,
error_budget: DataFrame,
futures: dict,
metrics: DataFrame | None = None,
settings: dict = dict(),
)
Every comparison of a simulated map with the observed one (validate()).
Attributes:
| Name | Type | Description |
|---|---|---|
three_map |
ThreeMapComparison
|
Pontius et al. (2011) at several resolutions ( |
similarity |
DataFrame
|
Fuzzy similarity of the differences per window (Dinamica EGO). |
kappa |
dict
|
MOLUSCE's kappa, kappa location and histogram, correctness... |
error_budget |
DataFrame
|
MOLUSCE's error budget at coarser resolutions. |
futures |
dict
|
Quantity and allocation disagreement, kappa simulation (FUTURES). |
metrics |
DataFrame or None
|
Landscape metrics of the observed and the simulated map side by side. |
summary
¶
The headline numbers: figure of merit, kappas, disagreement, similarity at each window.
Calibration¶
calibrate
¶
calibrate(
model,
start,
observed,
drivers=None,
*,
params: dict,
targets: dict | None = None,
method: str = "nelder-mead",
replicates: int = 1,
seed: int | None = None,
max_evals: int = 50,
grid_points: int = 5,
x0: dict | None = None,
n_workers: int | None = None,
nodata=None,
on=None,
n_threads: int | None = None,
) -> Calibration
Search the settings of a model that best reproduce an observed map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
A |
required | |
start
|
The map the simulation starts from and the observed map at its end. |
required | |
observed
|
The map the simulation starts from and the observed map at its end. |
required | |
drivers
|
The drivers of the model. |
None
|
|
params
|
dict
|
|
required |
targets
|
dict | None
|
|
None
|
method
|
('nelder-mead', 'differential-evolution', 'grid', 'random')
|
The search (scipy's optimizers; |
"nelder-mead"
|
replicates
|
int
|
Simulations per evaluation (their mean loss), seeds |
1
|
max_evals
|
int
|
Evaluations at most (not for |
50
|
x0
|
dict | None
|
Starting settings for Nelder-Mead (default: the middle of the ranges). |
None
|
on
|
|
None
|
Returns:
| Type | Description |
|---|---|
Calibration
|
|
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> S = ls.simulate
>>> rng = np.random.default_rng(0)
>>> d1, d2 = rng.random((30, 30)), rng.random((30, 30))
>>> land = np.ones((30, 30))
>>> demand = S.AreaDemand(np.array([[900, 0], [800, 100]]), classes=[1, 2])
>>> def build(w):
... pot = S.RasterPotential({1: np.full((30, 30), 0.5), 2: w * d1 + (1 - w) * d2})
... return S.Model(pot, demand, S.Ordered(order=[2, 1], stochastic=False))
>>> observed = build(0.3).run(land, seed=1).maps[-1]
>>> cal = S.calibrate(build, land, observed, params={"w": [0.0, 0.1, 0.2, 0.3, 0.4, 0.5]},
... targets={"figure_of_merit": 1}, method="grid")
>>> cal.best
{'w': 0.3}
Calibration
dataclass
¶
Calibration(
best: dict,
loss: float,
history: DataFrame,
model: object,
simulation: object,
settings: dict = dict(),
_maps: tuple = None,
)
The result of calibrate().
Attributes:
| Name | Type | Description |
|---|---|---|
best |
dict
|
The settings of lowest loss. |
loss |
float
|
Its loss. |
history |
DataFrame
|
Every evaluation: the settings, the loss and each target's part. |
model |
Model
|
The model with the best settings. |
simulation |
Simulation
|
Its first replicate. |
validate
¶
Every comparison of the best simulation with the observed map (validate()).
Presets and one-call projection¶
run
¶
run(
t0,
t1,
drivers=None,
*,
until=None,
years=None,
preset="dinamica",
seed=None,
validate=True,
dynamic=None,
rules=None,
nodata=None,
**options,
) -> Projection
Fit a land change model on two maps, check it on the observed period and project it.
- The preset is fitted on
t0 -> t1with one step per year; validate: the model simulates the observed period fromt0and its last map is compared witht1(agreement());until: the model projects fromt1, a step per year.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t0
|
The earlier and later maps. |
required | |
t1
|
The earlier and later maps. |
required | |
drivers
|
The explanatory variables. |
None
|
|
until
|
Last year of the projection (default: none, only the calibration). |
None
|
|
years
|
|
None
|
|
preset
|
|
'dinamica'
|
|
seed
|
Seed of every random draw (default: drawn, kept in |
None
|
|
validate
|
Simulate the observed period first and compare with |
True
|
|
dynamic
|
Dynamic variables and rules given to the model. |
None
|
|
rules
|
Dynamic variables and rules given to the model. |
None
|
|
options
|
The preset's settings (e.g. |
{}
|
Returns:
| Type | Description |
|---|---|
Projection
|
|
Examples:
>>> import landschaft as ls
>>> lu = ls.load_raster(ls.simulate.example_data("sibuyan_landuse"))
>>> drivers = ls.load_raster(ls.simulate.example_data("sibuyan_drivers"))[[0, 10]]
>>> proj = ls.simulate.run(lu[0], lu[1], drivers, until=2015, seed=1, woe=250) # maps of 1997 and 2011
>>> proj.maps["year"].values.tolist()
[2011, 2012, 2013, 2014, 2015]
>>> sorted(proj.report["stage"])
['fit', 'projection', 'validation']
Projection
dataclass
¶
Projection(
maps: DataArray,
calibration: object,
validation: dict | None,
model: object,
report: DataFrame,
settings: dict,
simulation: object = None,
_call: dict = None,
)
The result of run().
Attributes:
| Name | Type | Description |
|---|---|---|
maps |
DataArray
|
|
calibration |
Simulation or None
|
The simulation of the observed period, from the earlier map. |
validation |
dict or None
|
|
model |
Model
|
The fitted model. |
report |
DataFrame
|
What each stage did. |
settings |
dict
|
The choices (preset, years, seed, stage settings), enough to repeat the projection. |
redo
¶
redo(**changes) -> Projection
Run again from the same maps with some settings changed (proj.redo(seed=2, until=2060)).
validate
¶
Every comparison of the calibration's last map with the later map (validate()).
to_dataset
¶
The projected maps (and the calibration's, on its own year axis) as one Dataset.
agreement
¶
How a simulated map matches the observed one, cell by cell and in quantity.
Returns overall (share of cells of the same class), figure_of_merit
(hits / (hits + misses + false alarms + wrong hits), Pontius et al.
2008), the hits, misses, false_alarms and wrong_hits
(change simulated where it happened, to another class), and the
quantity difference of each class (simulated - observed cells).
presets
¶
The classic land change models, assembled from ls.simulate's pieces.
Each function returns a Model. The first four
take the pieces the model needs (they name the allocation and its usual
settings); dinamica, plus and molusce fit everything from two
maps and the drivers, as those programs do::
import landschaft as ls
S = ls.simulate
model = S.presets.dinamica(lc2010, lc2020, drivers, steps=10) # Markov + WoE + Expander/Patcher
sim = model.run(lc2010, drivers, seed=1)
clue_s
¶
CLUE-S (Verburg et al. 2002) as lulcc runs it: CLUE allocation of an AreaDemand.
clue are CLUE's settings (jitter, scale, max_iter,
max_diff, mean_diff); rules the Rules (elasticities,
conversion matrix, neighbourhood...).
ordered
¶
The ordered procedure of Fuchs et al. (2013), lulcc's OrderedModel.
clumondo
¶
CLUMondo: land systems meeting a ServiceDemand with their Supply.
settings are CLUMondo's (order, deviation,
speed, neighbourhood...); give the starting map or a
LandSystemStack to run.
futures
¶
FUTURES' patch growing (r.futures.simulation): PatchGrowing with a DevelopmentDemand.
observed_patches
¶
The patches of each observed transition: expansions of existing patches and new ones.
The cells that went from i to j are grouped in patches (8
neighbours); a patch touching a cell of j in t0 is an expansion
(Dinamica's Expander), the others are new patches (the Patcher).
Returns:
| Type | Description |
|---|---|
DataFrame
|
Per transition: |
dinamica
¶
dinamica(
t0,
t1,
drivers,
*,
steps: int = 1,
woe=None,
patches="observed",
isometry: float = 1.5,
prune_factor: float = 10.0,
rules=None,
dynamic=None,
nodata=None,
)
Dinamica EGO's classic model: Markov rates, weights of evidence, Expander and Patcher.
Fitted on two maps: the rates of each transition per step (the
steps-th root of the period's matrix), weights of evidence of every
observed transition (the ranges found automatically, or woe: the
WeightsOfEvidence settings, a number = the increment), and the
share of the Expander, the mean and variance of the patch sizes of each
transition measured on the observed change (observed_patches();
or patches: the ExpanderPatcher settings, a number = the
Expander's share); isometry is not measurable and given (Dinamica's
usual 1.5).
plus
¶
plus(
t0,
t1,
drivers,
*,
steps: int = 1,
estimator=None,
sample=0.05,
demand=None,
rules=None,
dynamic=None,
seed=None,
nodata=None,
**cars,
)
PLUS (Liang et al. 2021): LEAS (a random forest per class on the expansion areas) and CARS.
estimator is any scikit-learn classifier (default: a random forest
of 50 trees, the paper's), fitted for each class that expanded between
the maps on a sample of the cells (the paper's 5 %, drawn from
the expansion cells and the others separately); the demand is the Markov chain of the two maps projected
(PLUS's default) unless demand is given; cars are CARS's
settings.
molusce
¶
molusce(
t0,
t1,
drivers,
*,
bins=None,
iterations: int = 1,
unit_cell: float = 1,
dynamic=None,
nodata=None,
)
MOLUSCE: its weights of evidence (MolusceWoE) and simulator (MolusceCA).
Each iteration repeats the change observed between the maps (their cross table); start the run from the later map, as MOLUSCE does.
Running models with Terraflux¶
Terraflux runs land change models as graphs of operators, with the
same C++ kernels: Model.save writes a fitted model as its graph (with the data it runs on), Model.load reads
it back, and terraflux runs it with the terraflux command, giving the same maps as Model.run with the same
seed.
terraflux
¶
Run a Terraflux model with the terraflux command; the simulated maps (step, y, x).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
The model file ( |
required | |
out
|
The folder of the outputs (default: |
None
|
|
seed
|
Replace the model's seed. |
None
|
|
exe
|
The |
None
|
Returns:
| Type | Description |
|---|---|
DataArray
|
The |
Scenarios and replicates¶
scenarios
¶
scenarios(
model,
start=None,
drivers=None,
*,
scenarios=None,
replicates: int = 10,
seed=None,
steps=None,
years=None,
keep=None,
n_workers=None,
n_threads=None,
nodata=None,
on=None,
) -> Ensemble
Run a model under several scenarios, several times each, in parallel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
A |
required | |
start
|
The starting map (default: the projection's later map). |
None
|
|
drivers
|
The drivers (default: the projection's). |
None
|
|
scenarios
|
|
None
|
|
replicates
|
int
|
Runs per scenario; replicate |
10
|
steps
|
Steps to simulate, set on the allocation (default: the model's own). |
None
|
|
years
|
Year of the start (one step a year), or the years of all the maps. |
None
|
|
keep
|
The years to keep (default all): with large maps, e.g. |
None
|
|
n_workers
|
Runs at the same time (default: as many as the threads allow). |
None
|
|
n_threads
|
Threads in all (default: all cores but one), shared by the workers. |
None
|
|
on
|
|
None
|
Returns:
| Type | Description |
|---|---|
Ensemble
|
|
Examples:
>>> import numpy as np
>>> import landschaft as ls
>>> S = ls.simulate
>>> land = np.ones((30, 30)); land[10:20, 10:20] = 2
>>> pot = S.RasterPotential({1: np.full((30, 30), 0.2), 2: np.random.default_rng(0).random((30, 30))})
>>> demand = S.AreaDemand(np.array([[800, 100], [750, 150], [700, 200]]), classes=[1, 2])
>>> fast = S.AreaDemand(np.array([[800, 100], [700, 200], [600, 300]]), classes=[1, 2])
>>> ens = S.scenarios(S.Model(pot, demand, S.CARS(step_size=10)), land, replicates=4, seed=1,
... scenarios={"trend": {}, "fast": {"demand": fast}}, years=2020)
>>> ens.probability(2, year=2022).sel(scenario="fast").values.sum().round()
np.float64(300.0)
Ensemble
dataclass
¶
Ensemble(
scenarios: list,
seeds: list,
years: list,
classes: list,
codes: ndarray,
steps: DataFrame,
template: object = None,
)
The simulations of scenarios(): every scenario, replicate and year.
Attributes:
| Name | Type | Description |
|---|---|---|
scenarios |
list of str
|
|
seeds |
list of int
|
The seed of each replicate (the same in every scenario). |
years |
list
|
The year (or step) of each kept map, the start first. |
classes |
list
|
The classes of the maps. |
steps |
DataFrame
|
The step tables of every run, with |
probability
¶
Share of the replicates where each cell has the class: (scenario, year, y, x).
year and scenario select one (the dimension is dropped).
change_probability
¶
Share of the replicates where each cell's class differs from the start: (scenario, year, y, x).
metrics
¶
Landscape metrics (or any function of a map) of every run and year, in long format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metrics
|
Names of |
required | |
years
|
The years to measure (default: all kept). |
None
|
|
level
|
str
|
The level of the named metrics ( |
'class'
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
|
to_dataset
¶
Every map as one Dataset: landuse (scenario, replicate, year, y, x).
Example data¶
example_data
¶
Path of an example data set for land change modelling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Plum Island Ecosystems, Massachusetts (Pontius & Parmentier 2014; 434 x 497 cells of ~100 m, EPSG:26986):
Sibuyan Island, Philippines (Verburg et al. 2002, the CLUE-S demonstration data, modified for teaching: not for other uses; 108 x 128 cells of 250 m, no CRS):
Sao Lourenco River Basin, Mato Grosso, Brazil ( |
'sibuyan_landuse'
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Examples: