Quickstart: demographic vulnerability from a projection matrix
This notebook introduces the basic workflow in demovuln:
define a matrix population model;
simulate one temporally structured perturbation;
compute population reduction relative to the unperturbed baseline;
evaluate a perturbation grid and compute integrated vulnerability (\Phi).
The example uses a two-stage projection matrix for clarity. Columns are source stages at time (t), and rows are destination stages at time (t+1).
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from demovuln import (
MatrixPopulationModel,
PerturbationGrid,
simulate_dynamics,
run_grid,
)
Define a matrix population model
In this example, stage 0 is a juvenile/pre-reproductive stage and stage 1 is an adult/reproductive stage. The fecundity entry is in the first row, second column.
A = np.array([
[0.0, 2.0],
[0.4, 0.7],
])
model = MatrixPopulationModel(
A,
adult_stages=[1],
juvenile_stages=[0],
name="two_stage_example",
)
print(f"Dominant eigenvalue: {model.lambda_:.3f}")
print(f"Number of stages: {model.n_stages}")
print("Stable stage distribution:", model.stable_distribution())
Simulate a single perturbation regime
The following simulation applies a 25% reduction to adult survival for one projection interval every three projection intervals. The final population reduction is computed relative to the unperturbed baseline trajectory at the same final time step.
sim = simulate_dynamics(
model,
target="adult_survival",
magnitude=0.25,
duration=1,
period=3,
t_max=50,
recovery_steps=10,
)
print(f"Population reduction: {sim.reduction:.2f}%")
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(sim.baseline_abundance, label="Unperturbed baseline")
ax.plot(sim.abundance, label="Perturbed dynamics")
ax.set_xlabel("Time step")
ax.set_ylabel("Relative population size")
ax.set_title("Population trajectory under adult-survival perturbations")
ax.legend()
fig.tight_layout()
plt.show()
Evaluate a perturbation grid
We now evaluate multiple combinations of perturbation magnitude, duration, and period. Integrated vulnerability (\Phi) is the mean population reduction across the feasible perturbation regimes in the grid.
grid = PerturbationGrid(
magnitudes=np.linspace(0, 1, 11),
durations=[0, 1, 2, 3],
periods=[1, 2, 3, 5, 10],
)
out = run_grid(
model,
target="adult_survival",
grid=grid,
t_max=50,
recovery_steps=10,
)
print(f"Integrated vulnerability Φ: {out.vulnerability:.2f}%")
out.table.head()
Compare demographic targets
The same perturbation space can be applied to different demographic targets. This provides a direct comparison of vulnerability to perturbations affecting adult survival, juvenile survival, and fecundity.
summaries = []
for target in ["adult_survival", "juvenile_survival", "fecundity", "all"]:
result = run_grid(
model,
target=target,
grid=grid,
t_max=50,
recovery_steps=10,
)
summaries.append({"target": target, "vulnerability": result.vulnerability})
summary = pd.DataFrame(summaries)
summary
fig, ax = plt.subplots(figsize=(7, 4))
ax.bar(summary["target"], summary["vulnerability"])
ax.set_xlabel("Perturbed demographic target")
ax.set_ylabel("Integrated vulnerability Φ (%)")
ax.set_title("Vulnerability across demographic targets")
ax.tick_params(axis="x", rotation=30)
fig.tight_layout()
plt.show()
Export grid results
The long-format table returned by run_grid is convenient for downstream analyses and plotting.
out.table.to_csv("quickstart_adult_survival_grid.csv", index=False)
print("Saved quickstart_adult_survival_grid.csv")