Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/ecology/metapopulation.tex
51 views
unlisted
1
\documentclass[11pt,a4paper]{article}
2
\usepackage[utf8]{inputenc}
3
\usepackage[T1]{fontenc}
4
\usepackage{amsmath,amssymb}
5
\usepackage{graphicx}
6
\usepackage{booktabs}
7
\usepackage{siunitx}
8
\usepackage{geometry}
9
\geometry{margin=1in}
10
\usepackage{pythontex}
11
\usepackage{hyperref}
12
\usepackage{float}
13
\usepackage{natbib}
14
15
\title{Metapopulation Dynamics: Patch Occupancy Models and Spatial Population Persistence}
16
\author{Ecology Research Group}
17
\date{\today}
18
19
\begin{document}
20
\maketitle
21
22
\begin{abstract}
23
Metapopulation theory provides a framework for understanding how spatial population structure influences persistence in fragmented landscapes. We analyze the classical Levins model of patch occupancy dynamics, where local populations experience colonization and extinction at the patch level. Using computational simulations, we examine equilibrium occupancy patterns, extinction thresholds, and the critical colonization-extinction ratio that determines metapopulation viability. Our analysis reveals that the metapopulation equilibrium $p^* = 1 - e/c$ requires colonization rates ($c$) to exceed extinction rates ($e$) for persistence, with spatial connectivity playing a crucial role in maintaining population networks. We extend the analysis to source-sink dynamics, incidence functions dependent on patch characteristics, and extinction debt following habitat loss. Results demonstrate that metapopulations can persist regionally despite local extinctions when sufficient patch connectivity maintains colonization-extinction balance, but face collapse when habitat destruction reduces metapopulation capacity below critical thresholds.
24
\end{abstract}
25
26
\section{Introduction}
27
28
Habitat fragmentation has become one of the most pervasive threats to biodiversity, creating landscapes where species persist as collections of local populations connected by dispersal rather than as single continuous populations \citep{hanski1999metapopulation, fahrig2003effects}. Metapopulation theory, pioneered by \citet{levins1969some}, provides a mathematical framework for understanding how populations survive in fragmented landscapes through the balance of local extinction and recolonization.
29
30
In classical metapopulation models, a species occupies a network of discrete habitat patches, each supporting a local population that faces some probability of extinction. Regional persistence depends not on the permanence of any single local population, but on the rate at which extinct patches are recolonized from occupied patches. This fundamental insight—that populations can persist regionally despite local instability—has profound implications for conservation in fragmented landscapes \citep{hanski1998metapopulation}.
31
32
The simplest metapopulation model, proposed by Levins, tracks the fraction of occupied patches $p(t)$ through time. Occupied patches generate colonists at rate $c$ that establish new populations in empty patches, while occupied patches go extinct at rate $e$. This yields the differential equation:
33
\begin{equation}
34
\frac{dp}{dt} = cp(1-p) - ep
35
\end{equation}
36
37
This deceptively simple model reveals critical thresholds for persistence and provides a foundation for understanding more complex spatial population dynamics.
38
39
\begin{pycode}
40
import numpy as np
41
import matplotlib.pyplot as plt
42
from scipy.integrate import odeint
43
from scipy.optimize import fsolve
44
import matplotlib.patches as mpatches
45
46
plt.rcParams['text.usetex'] = True
47
plt.rcParams['font.family'] = 'serif'
48
plt.rcParams['font.size'] = 10
49
50
# Levins model parameters
51
colonization_rate = 0.15 # colonization rate per patch
52
extinction_rate = 0.05 # extinction rate per patch
53
time_horizon = 200 # years
54
55
# Initial conditions for different scenarios
56
initial_occupancy_low = 0.1
57
initial_occupancy_high = 0.9
58
\end{pycode}
59
60
\section{The Levins Metapopulation Model}
61
62
The Levins model describes metapopulation dynamics in terms of patch occupancy rather than population sizes. Let $p(t)$ represent the fraction of patches occupied at time $t$. The model assumes:
63
64
\begin{enumerate}
65
\item All patches are identical and equally connected
66
\item Colonization rate is proportional to both occupied patches (source of colonists) and empty patches (targets for colonization)
67
\item Extinction occurs at constant rate $e$ per occupied patch
68
\item Dynamics are much faster at the patch level than at the metapopulation level
69
\end{enumerate}
70
71
\subsection{Model Derivation and Equilibrium}
72
73
The rate of change in patch occupancy has two components:
74
\begin{itemize}
75
\item \textbf{Colonization}: Empty patches $(1-p)$ are colonized at rate $cp$, where $c$ is the colonization coefficient
76
\item \textbf{Extinction}: Occupied patches $p$ go extinct at rate $e$
77
\end{itemize}
78
79
This yields:
80
\begin{equation}
81
\frac{dp}{dt} = \underbrace{cp(1-p)}_{\text{colonization}} - \underbrace{ep}_{\text{extinction}}
82
\end{equation}
83
84
At equilibrium, $\frac{dp}{dt} = 0$, giving:
85
\begin{equation}
86
cp^*(1-p^*) = ep^*
87
\end{equation}
88
89
This has two solutions:
90
\begin{enumerate}
91
\item $p^* = 0$ (extinction)
92
\item $p^* = 1 - \frac{e}{c}$ (coexistence)
93
\end{enumerate}
94
95
The coexistence equilibrium exists only when $c > e$, revealing a critical threshold: \textbf{the colonization rate must exceed the extinction rate for metapopulation persistence}.
96
97
\begin{pycode}
98
# Levins model differential equation
99
def levins_model(p, t, c, e):
100
"""
101
Levins metapopulation model
102
103
Parameters:
104
p : float - fraction of patches occupied
105
t : float - time
106
c : float - colonization rate
107
e : float - extinction rate
108
109
Returns:
110
dpdt : float - rate of change in patch occupancy
111
"""
112
dpdt = c * p * (1 - p) - e * p
113
return dpdt
114
115
# Time array
116
time_points = np.linspace(0, time_horizon, 1000)
117
118
# Solve for different initial conditions
119
trajectory_low = odeint(levins_model, initial_occupancy_low, time_points,
120
args=(colonization_rate, extinction_rate))
121
trajectory_high = odeint(levins_model, initial_occupancy_high, time_points,
122
args=(colonization_rate, extinction_rate))
123
124
# Calculate equilibrium
125
equilibrium_occupancy = 1 - extinction_rate / colonization_rate
126
127
# Extinction threshold (c = e)
128
extinction_threshold = extinction_rate
129
130
# Create figure showing Levins model dynamics
131
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
132
133
# Panel A: Time series from different initial conditions
134
ax1.plot(time_points, trajectory_low, 'b-', linewidth=2, label='Initial $p_0 = 0.1$')
135
ax1.plot(time_points, trajectory_high, 'r-', linewidth=2, label='Initial $p_0 = 0.9$')
136
ax1.axhline(equilibrium_occupancy, color='black', linestyle='--', linewidth=1.5,
137
label=f'Equilibrium $p^* = {equilibrium_occupancy:.3f}$')
138
ax1.set_xlabel('Time (years)', fontsize=12)
139
ax1.set_ylabel('Patch Occupancy Fraction $p(t)$', fontsize=12)
140
ax1.set_title('Levins Model: Convergence to Equilibrium', fontsize=13, weight='bold')
141
ax1.legend(loc='best', fontsize=10)
142
ax1.grid(True, alpha=0.3)
143
ax1.set_xlim(0, time_horizon)
144
ax1.set_ylim(0, 1)
145
146
# Panel B: Phase portrait showing dp/dt vs p
147
p_range = np.linspace(0, 1, 200)
148
dpdt_range = colonization_rate * p_range * (1 - p_range) - extinction_rate * p_range
149
150
ax2.plot(p_range, dpdt_range, 'darkgreen', linewidth=2.5)
151
ax2.axhline(0, color='black', linestyle='-', linewidth=0.8)
152
ax2.axvline(equilibrium_occupancy, color='red', linestyle='--', linewidth=1.5,
153
label=f'Stable equilibrium $p^* = {equilibrium_occupancy:.3f}$')
154
ax2.axvline(0, color='gray', linestyle='--', linewidth=1.5, alpha=0.5,
155
label='Unstable equilibrium (extinction)')
156
ax2.fill_between(p_range, 0, dpdt_range, where=(dpdt_range > 0),
157
alpha=0.2, color='green', label='$dp/dt > 0$ (increasing)')
158
ax2.fill_between(p_range, 0, dpdt_range, where=(dpdt_range < 0),
159
alpha=0.2, color='red', label='$dp/dt < 0$ (decreasing)')
160
ax2.set_xlabel('Patch Occupancy $p$', fontsize=12)
161
ax2.set_ylabel('Rate of Change $dp/dt$', fontsize=12)
162
ax2.set_title('Phase Portrait: Colonization-Extinction Balance', fontsize=13, weight='bold')
163
ax2.legend(loc='best', fontsize=9)
164
ax2.grid(True, alpha=0.3)
165
166
plt.tight_layout()
167
plt.savefig('metapopulation_levins_dynamics.pdf', dpi=300, bbox_inches='tight')
168
plt.close()
169
\end{pycode}
170
171
\begin{figure}[H]
172
\centering
173
\includegraphics[width=0.98\textwidth]{metapopulation_levins_dynamics.pdf}
174
\caption{Levins metapopulation model dynamics with colonization rate $c = \py{colonization_rate}$ and extinction rate $e = \py{extinction_rate}$. \textbf{Left panel}: Time trajectories from different initial occupancy levels converge to the stable equilibrium $p^* = \py{f'{equilibrium_occupancy:.3f}'}$, demonstrating that metapopulation persistence depends on the colonization-extinction balance rather than initial conditions. \textbf{Right panel}: Phase portrait showing the rate of change in patch occupancy as a function of current occupancy. The system has two equilibria: an unstable extinction state at $p = 0$ and a stable coexistence equilibrium at $p^* = 1 - e/c = \py{f'{equilibrium_occupancy:.3f}'}$. Green shading indicates regions where patch occupancy increases (colonization exceeds extinction), while red shading shows where occupancy decreases. The stable equilibrium represents a balance where colonization of empty patches exactly compensates for extinction of occupied patches.}
175
\end{figure}
176
177
\subsection{Extinction Threshold and Metapopulation Capacity}
178
179
The critical condition for persistence, $c > e$, can be interpreted in terms of metapopulation capacity. When colonization rates fall below extinction rates, the only stable equilibrium is complete extinction. This threshold has important conservation implications: habitat destruction that reduces connectivity (and thus colonization rates) can push metapopulations below the extinction threshold even when suitable habitat remains.
180
181
\begin{pycode}
182
# Analyze extinction threshold
183
colonization_range = np.linspace(0, 0.3, 100)
184
equilibria = np.zeros_like(colonization_range)
185
186
for i, c in enumerate(colonization_range):
187
if c > extinction_rate:
188
equilibria[i] = 1 - extinction_rate / c
189
else:
190
equilibria[i] = 0 # extinction
191
192
# Create threshold analysis figure
193
fig, ax = plt.subplots(figsize=(10, 6))
194
195
# Plot equilibrium occupancy vs colonization rate
196
ax.plot(colonization_range, equilibria, 'b-', linewidth=3, label='Equilibrium occupancy $p^*$')
197
ax.axvline(extinction_rate, color='red', linestyle='--', linewidth=2,
198
label=f'Extinction threshold $c = e = {extinction_rate}$')
199
ax.axhline(equilibrium_occupancy, color='green', linestyle=':', linewidth=2,
200
label=f'Current equilibrium $p^* = {equilibrium_occupancy:.3f}$')
201
202
# Shade regions
203
ax.fill_between(colonization_range, 0, 1,
204
where=(colonization_range <= extinction_rate),
205
alpha=0.2, color='red', label='Extinction region ($c < e$)')
206
ax.fill_between(colonization_range, 0, equilibria,
207
where=(colonization_range > extinction_rate),
208
alpha=0.2, color='green', label='Persistence region ($c > e$)')
209
210
ax.set_xlabel('Colonization Rate $c$ (per patch per year)', fontsize=12)
211
ax.set_ylabel('Equilibrium Patch Occupancy $p^*$', fontsize=12)
212
ax.set_title('Extinction Threshold: Metapopulation Viability vs Colonization Rate',
213
fontsize=13, weight='bold')
214
ax.legend(loc='lower right', fontsize=10)
215
ax.grid(True, alpha=0.3)
216
ax.set_xlim(0, 0.3)
217
ax.set_ylim(0, 1)
218
219
# Add annotation
220
ax.annotate('Metapopulation\ncollapse',
221
xy=(extinction_rate * 0.5, 0.05), fontsize=11,
222
ha='center', color='darkred', weight='bold')
223
ax.annotate('Stable\npersistence',
224
xy=(0.22, 0.8), fontsize=11,
225
ha='center', color='darkgreen', weight='bold')
226
227
plt.tight_layout()
228
plt.savefig('metapopulation_extinction_threshold.pdf', dpi=300, bbox_inches='tight')
229
plt.close()
230
231
# Calculate some key metrics
232
critical_ratio = colonization_rate / extinction_rate
233
doubling_time_approx = np.log(2) / (colonization_rate - extinction_rate)
234
\end{pycode}
235
236
\begin{figure}[H]
237
\centering
238
\includegraphics[width=0.85\textwidth]{metapopulation_extinction_threshold.pdf}
239
\caption{Extinction threshold in the Levins metapopulation model showing equilibrium patch occupancy as a function of colonization rate. When colonization rate $c$ exceeds extinction rate $e = \py{extinction_rate}$, the metapopulation persists at equilibrium occupancy $p^* = 1 - e/c$. Below this critical threshold (red shaded region), the only stable state is complete extinction ($p^* = 0$). The current parameter set yields a colonization-extinction ratio of $c/e = \py{f'{critical_ratio:.2f}'}$, supporting metapopulation persistence at $p^* = \py{f'{equilibrium_occupancy:.3f}'}$ occupancy. This demonstrates that habitat fragmentation affecting colonization rates can drive metapopulation collapse even when local extinction rates remain constant, a phenomenon critical to understanding species decline in fragmented landscapes.}
240
\end{figure}
241
242
\section{Patch Size Effects and Incidence Functions}
243
244
Real metapopulations violate the Levins assumption of patch homogeneity. Larger patches typically support larger populations with lower extinction probabilities, while isolation reduces colonization probability. These effects can be captured through \textbf{incidence functions} that predict patch occupancy based on patch characteristics.
245
246
\subsection{Area-Dependent Extinction and Colonization}
247
248
Extinction probability typically decreases with patch area (larger populations are more stable), while colonization probability increases with patch connectivity. We can model these effects as:
249
250
\begin{align}
251
\text{Extinction rate: } & e_i = e_0 \cdot A_i^{-x} \\
252
\text{Colonization rate: } & c_i = c_0 \cdot S_i \cdot A_i^{y}
253
\end{align}
254
255
where $A_i$ is patch area, $S_i$ is a connectivity metric, and $x, y$ are scaling exponents.
256
257
\begin{pycode}
258
# Simulate patch network with heterogeneous sizes
259
np.random.seed(42)
260
num_patches = 50
261
patch_areas = np.random.gamma(2, 2, num_patches) # heterogeneous patch sizes
262
patch_distances = np.random.uniform(0.5, 5, num_patches) # isolation distances
263
264
# Area-dependent extinction rates (smaller patches = higher extinction)
265
extinction_exponent = 0.5
266
baseline_extinction = 0.1
267
patch_extinction_rates = baseline_extinction * patch_areas**(-extinction_exponent)
268
269
# Distance-dependent colonization (isolated patches = lower colonization)
270
distance_decay = 0.5
271
baseline_colonization = 0.2
272
patch_connectivity = np.exp(-distance_decay * patch_distances)
273
patch_colonization_rates = baseline_colonization * patch_connectivity * patch_areas**0.3
274
275
# Calculate expected incidence (equilibrium occupancy probability) for each patch
276
patch_incidence = np.zeros(num_patches)
277
for i in range(num_patches):
278
if patch_colonization_rates[i] > patch_extinction_rates[i]:
279
patch_incidence[i] = 1 - patch_extinction_rates[i] / patch_colonization_rates[i]
280
else:
281
patch_incidence[i] = 0
282
283
# Sort by patch area for visualization
284
sort_idx = np.argsort(patch_areas)
285
sorted_areas = patch_areas[sort_idx]
286
sorted_incidence = patch_incidence[sort_idx]
287
sorted_extinction = patch_extinction_rates[sort_idx]
288
sorted_colonization = patch_colonization_rates[sort_idx]
289
290
# Create incidence function plot
291
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(12, 10))
292
293
# Panel A: Extinction rate vs patch area
294
ax1.scatter(patch_areas, patch_extinction_rates, c='red', s=60, alpha=0.6, edgecolors='darkred')
295
area_range = np.linspace(0.5, max(patch_areas), 100)
296
predicted_extinction = baseline_extinction * area_range**(-extinction_exponent)
297
ax1.plot(area_range, predicted_extinction, 'r--', linewidth=2,
298
label=f'$e(A) = {baseline_extinction:.2f} \\cdot A^{{-{extinction_exponent}}}$')
299
ax1.set_xlabel('Patch Area (ha)', fontsize=11)
300
ax1.set_ylabel('Extinction Rate (per year)', fontsize=11)
301
ax1.set_title('A) Area-Dependent Extinction', fontsize=12, weight='bold')
302
ax1.legend(loc='upper right', fontsize=10)
303
ax1.grid(True, alpha=0.3)
304
305
# Panel B: Colonization rate vs isolation
306
ax2.scatter(patch_distances, patch_colonization_rates, c='blue', s=60, alpha=0.6, edgecolors='darkblue')
307
dist_range = np.linspace(min(patch_distances), max(patch_distances), 100)
308
predicted_connectivity = np.exp(-distance_decay * dist_range)
309
ax2.plot(dist_range, baseline_colonization * predicted_connectivity * np.mean(patch_areas)**0.3,
310
'b--', linewidth=2, label=f'$c(d) \\propto e^{{-{distance_decay} d}}$')
311
ax2.set_xlabel('Isolation Distance (km)', fontsize=11)
312
ax2.set_ylabel('Colonization Rate (per year)', fontsize=11)
313
ax2.set_title('B) Distance-Dependent Colonization', fontsize=12, weight='bold')
314
ax2.legend(loc='upper right', fontsize=10)
315
ax2.grid(True, alpha=0.3)
316
317
# Panel C: Incidence function
318
ax3.scatter(sorted_areas, sorted_incidence, c=sorted_incidence, cmap='RdYlGn',
319
s=80, alpha=0.7, edgecolors='black', vmin=0, vmax=1)
320
ax3.axhline(0.5, color='gray', linestyle='--', linewidth=1, alpha=0.5)
321
ax3.set_xlabel('Patch Area (ha)', fontsize=11)
322
ax3.set_ylabel('Occupancy Probability (Incidence)', fontsize=11)
323
ax3.set_title('C) Incidence Function: Occupancy vs Area', fontsize=12, weight='bold')
324
ax3.grid(True, alpha=0.3)
325
ax3.set_ylim(-0.05, 1.05)
326
327
# Panel D: Colonization-extinction ratio
328
ce_ratio = patch_colonization_rates / patch_extinction_rates
329
ax4.scatter(patch_areas, ce_ratio, c=patch_incidence, cmap='RdYlGn',
330
s=80, alpha=0.7, edgecolors='black', vmin=0, vmax=1)
331
ax4.axhline(1, color='red', linestyle='--', linewidth=2, label='Extinction threshold ($c/e = 1$)')
332
ax4.set_xlabel('Patch Area (ha)', fontsize=11)
333
ax4.set_ylabel('Colonization/Extinction Ratio ($c/e$)', fontsize=11)
334
ax4.set_title('D) Viability Threshold by Patch Size', fontsize=12, weight='bold')
335
ax4.legend(loc='upper left', fontsize=10)
336
ax4.grid(True, alpha=0.3)
337
ax4.set_yscale('log')
338
339
plt.tight_layout()
340
plt.savefig('metapopulation_incidence_functions.pdf', dpi=300, bbox_inches='tight')
341
plt.close()
342
343
# Calculate summary statistics
344
mean_patch_area = np.mean(patch_areas)
345
viable_patch_fraction = np.sum(patch_incidence > 0) / num_patches
346
mean_incidence = np.mean(patch_incidence[patch_incidence > 0])
347
\end{pycode}
348
349
\begin{figure}[H]
350
\centering
351
\includegraphics[width=0.98\textwidth]{metapopulation_incidence_functions.pdf}
352
\caption{Incidence functions showing how patch characteristics affect metapopulation dynamics in a heterogeneous landscape of \py{num_patches} habitat patches. \textbf{Panel A}: Extinction rate decreases with patch area following a power law $e(A) \propto A^{-0.5}$, as larger patches support larger, more stable populations. \textbf{Panel B}: Colonization rate declines exponentially with isolation distance due to reduced dispersal success. \textbf{Panel C}: Equilibrium occupancy probability (incidence) increases with patch area, with small or isolated patches showing near-zero occupancy while large, connected patches approach certainty of occupancy. \textbf{Panel D}: The colonization-extinction ratio $c/e$ determines patch viability; patches below the threshold (red line, $c/e = 1$) cannot sustain populations independently. In this simulated landscape, \py{f'{viable_patch_fraction*100:.1f}'}\% of patches have positive incidence, with mean occupancy probability \py{f'{mean_incidence:.3f}'} for viable patches, demonstrating that landscape heterogeneity creates a gradient of patch quality rather than uniform occupancy patterns.}
353
\end{figure}
354
355
\section{Source-Sink Dynamics and Rescue Effects}
356
357
In heterogeneous landscapes, some patches may have negative intrinsic growth rates (sinks) but persist through immigration from high-quality patches (sources). This \textbf{source-sink dynamics} extends classical metapopulation theory by recognizing that occupied patches may depend on dispersal for persistence, not just recolonization after extinction.
358
359
\subsection{The Rescue Effect}
360
361
High immigration rates can reduce effective extinction probability through demographic rescue, where immigrants prevent small populations from stochastic extinction. This creates a positive feedback: high patch occupancy increases connectivity, which reduces extinction rates, which maintains high occupancy.
362
363
\begin{pycode}
364
# Source-sink metapopulation model
365
def source_sink_dynamics(state, t, patch_quality, connectivity_matrix, base_extinction):
366
"""
367
Metapopulation model with source-sink dynamics and rescue effect
368
369
Parameters:
370
state : array - occupancy probability for each patch
371
t : float - time
372
patch_quality : array - intrinsic growth rate for each patch
373
connectivity_matrix : array - dispersal connectivity between patches
374
base_extinction : float - baseline extinction rate
375
376
Returns:
377
dpdt : array - rate of change for each patch
378
"""
379
n_patches = len(state)
380
dpdt = np.zeros(n_patches)
381
382
for i in range(n_patches):
383
# Colonization from all occupied patches
384
colonization = 0
385
for j in range(n_patches):
386
if i != j:
387
colonization += connectivity_matrix[j, i] * state[j]
388
389
# Extinction with rescue effect (immigration reduces extinction)
390
immigration = np.sum(connectivity_matrix[:, i] * state)
391
rescue_factor = 1 - 0.7 * min(immigration, 1) # immigration reduces extinction
392
extinction = base_extinction * rescue_factor
393
394
# Dynamics including patch quality
395
if state[i] > 0.01: # if occupied
396
dpdt[i] = colonization * (1 - state[i]) + patch_quality[i] * state[i] - extinction * state[i]
397
else:
398
dpdt[i] = colonization * (1 - state[i])
399
400
return dpdt
401
402
# Create patch network with sources and sinks
403
n_patches_network = 20
404
patch_quality_gradient = np.linspace(-0.05, 0.1, n_patches_network) # some negative (sinks)
405
406
# Connectivity matrix (distance-based dispersal)
407
connectivity_strength = 0.08
408
connectivity_matrix = np.zeros((n_patches_network, n_patches_network))
409
for i in range(n_patches_network):
410
for j in range(n_patches_network):
411
if i != j:
412
distance = abs(i - j)
413
connectivity_matrix[i, j] = connectivity_strength * np.exp(-0.3 * distance)
414
415
# Simulate dynamics
416
time_network = np.linspace(0, 300, 1000)
417
initial_state = np.random.uniform(0.3, 0.7, n_patches_network)
418
trajectory_network = odeint(source_sink_dynamics, initial_state, time_network,
419
args=(patch_quality_gradient, connectivity_matrix, 0.08))
420
421
# Identify sources and sinks at equilibrium
422
equilibrium_state = trajectory_network[-1, :]
423
source_patches = patch_quality_gradient > 0
424
sink_patches = patch_quality_gradient < 0
425
426
# Create source-sink visualization
427
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(12, 10))
428
429
# Panel A: Patch quality gradient
430
patch_ids = np.arange(n_patches_network)
431
colors = ['red' if q < 0 else 'green' for q in patch_quality_gradient]
432
ax1.bar(patch_ids, patch_quality_gradient, color=colors, alpha=0.6, edgecolor='black')
433
ax1.axhline(0, color='black', linestyle='-', linewidth=1)
434
ax1.set_xlabel('Patch ID', fontsize=11)
435
ax1.set_ylabel('Intrinsic Growth Rate $r_i$', fontsize=11)
436
ax1.set_title('A) Source-Sink Gradient', fontsize=12, weight='bold')
437
ax1.grid(True, alpha=0.3, axis='y')
438
sink_patch = mpatches.Patch(color='red', alpha=0.6, label='Sink patches ($r < 0$)')
439
source_patch = mpatches.Patch(color='green', alpha=0.6, label='Source patches ($r > 0$)')
440
ax1.legend(handles=[source_patch, sink_patch], loc='lower right')
441
442
# Panel B: Occupancy time series for sources vs sinks
443
source_mean = np.mean(trajectory_network[:, source_patches], axis=1)
444
sink_mean = np.mean(trajectory_network[:, sink_patches], axis=1)
445
ax2.plot(time_network, source_mean, 'g-', linewidth=2.5, label='Source patches (mean)')
446
ax2.plot(time_network, sink_mean, 'r-', linewidth=2.5, label='Sink patches (mean)')
447
ax2.set_xlabel('Time (years)', fontsize=11)
448
ax2.set_ylabel('Mean Patch Occupancy', fontsize=11)
449
ax2.set_title('B) Source vs Sink Dynamics', fontsize=12, weight='bold')
450
ax2.legend(loc='best', fontsize=10)
451
ax2.grid(True, alpha=0.3)
452
ax2.set_ylim(0, 1)
453
454
# Panel C: Equilibrium occupancy vs patch quality
455
ax3.scatter(patch_quality_gradient[source_patches], equilibrium_state[source_patches],
456
c='green', s=100, alpha=0.7, edgecolors='black', label='Source patches')
457
ax3.scatter(patch_quality_gradient[sink_patches], equilibrium_state[sink_patches],
458
c='red', s=100, alpha=0.7, edgecolors='black', label='Sink patches')
459
ax3.axvline(0, color='black', linestyle='--', linewidth=1.5)
460
ax3.set_xlabel('Intrinsic Growth Rate $r_i$', fontsize=11)
461
ax3.set_ylabel('Equilibrium Occupancy Probability', fontsize=11)
462
ax3.set_title('C) Occupancy vs Patch Quality', fontsize=12, weight='bold')
463
ax3.legend(loc='best', fontsize=10)
464
ax3.grid(True, alpha=0.3)
465
466
# Panel D: Connectivity network diagram
467
ax4.set_xlim(-1, n_patches_network)
468
ax4.set_ylim(-0.5, 1.5)
469
y_pos = equilibrium_state
470
for i in range(n_patches_network):
471
color = 'green' if patch_quality_gradient[i] > 0 else 'red'
472
ax4.scatter(i, 1, s=y_pos[i]*500, c=color, alpha=0.7, edgecolors='black', linewidth=2)
473
474
# Draw connections
475
for j in range(i+1, min(i+4, n_patches_network)):
476
if connectivity_matrix[i, j] > 0.01:
477
ax4.plot([i, j], [1, 1], 'gray', alpha=0.3, linewidth=connectivity_matrix[i, j]*50)
478
479
ax4.set_xlabel('Patch Position', fontsize=11)
480
ax4.set_title('D) Network Structure (circle size = occupancy)', fontsize=12, weight='bold')
481
ax4.set_yticks([])
482
ax4.grid(True, alpha=0.3, axis='x')
483
484
plt.tight_layout()
485
plt.savefig('metapopulation_source_sink.pdf', dpi=300, bbox_inches='tight')
486
plt.close()
487
488
# Calculate source-sink statistics
489
source_occupancy = np.mean(equilibrium_state[source_patches])
490
sink_occupancy = np.mean(equilibrium_state[sink_patches])
491
num_sinks = np.sum(sink_patches)
492
num_sources = np.sum(source_patches)
493
\end{pycode}
494
495
\begin{figure}[H]
496
\centering
497
\includegraphics[width=0.98\textwidth]{metapopulation_source_sink.pdf}
498
\caption{Source-sink metapopulation dynamics in a landscape with heterogeneous patch quality. \textbf{Panel A}: Patch quality gradient showing \py{num_sources} source patches (positive intrinsic growth, green) and \py{num_sinks} sink patches (negative intrinsic growth, red) where local populations cannot persist without immigration. \textbf{Panel B}: Time series showing source patches maintain higher mean occupancy (\py{f'{source_occupancy:.3f}'}) than sink patches (\py{f'{sink_occupancy:.3f}'}), but sinks persist through continuous immigration rather than going extinct. \textbf{Panel C}: Equilibrium occupancy increases with patch quality, but sink patches show non-zero occupancy due to rescue effects from dispersal. \textbf{Panel D}: Network connectivity structure where circle size represents occupancy probability and gray lines show dispersal connections. This demonstrates that metapopulation persistence depends not only on suitable habitat area but on spatial configuration that allows source populations to subsidize demographic sinks, a critical consideration for reserve network design.}
499
\end{figure}
500
501
\section{Extinction Debt and Habitat Loss}
502
503
When habitat destruction reduces the number of patches, metapopulation occupancy does not decline instantly to the new equilibrium. Instead, there is a transient period during which patch occupancy exceeds the sustainable level for the reduced habitat network. This creates an \textbf{extinction debt}: species that appear viable are actually committed to future decline.
504
505
\begin{pycode}
506
# Simulate habitat loss scenario
507
def habitat_loss_dynamics(t):
508
"""
509
Simulate gradual habitat loss reducing colonization rate
510
"""
511
if t < 100:
512
return colonization_rate # initial rate
513
elif t < 120:
514
# Gradual habitat loss over 20 years
515
loss_fraction = (t - 100) / 20
516
return colonization_rate * (1 - 0.6 * loss_fraction)
517
else:
518
return colonization_rate * 0.4 # 60% habitat loss
519
520
def levins_habitat_loss(p, t, e):
521
"""Levins model with time-varying colonization due to habitat loss"""
522
c_t = habitat_loss_dynamics(t)
523
dpdt = c_t * p * (1 - p) - e * p
524
return dpdt
525
526
# Simulate extinction debt
527
time_extinction_debt = np.linspace(0, 300, 2000)
528
initial_p = equilibrium_occupancy
529
trajectory_habitat_loss = odeint(levins_habitat_loss, initial_p, time_extinction_debt,
530
args=(extinction_rate,))
531
532
# Calculate new equilibrium after habitat loss
533
new_colonization = colonization_rate * 0.4
534
if new_colonization > extinction_rate:
535
new_equilibrium = 1 - extinction_rate / new_colonization
536
else:
537
new_equilibrium = 0
538
539
# Calculate extinction debt (difference between current and equilibrium occupancy)
540
extinction_debt = np.zeros_like(time_extinction_debt)
541
for i, t in enumerate(time_extinction_debt):
542
if t < 100:
543
target_equilibrium = equilibrium_occupancy
544
else:
545
target_equilibrium = new_equilibrium
546
extinction_debt[i] = max(0, trajectory_habitat_loss[i, 0] - target_equilibrium)
547
548
# Time to half-life of debt
549
if new_equilibrium > 0:
550
half_debt_occupancy = (trajectory_habitat_loss[2000, 0] + new_equilibrium) / 2
551
half_life_idx = np.where(trajectory_habitat_loss[1000:, 0] < half_debt_occupancy)[0]
552
if len(half_life_idx) > 0:
553
half_life_time = time_extinction_debt[1000 + half_life_idx[0]] - 120
554
else:
555
half_life_time = np.inf
556
else:
557
half_life_time = np.inf
558
559
# Create extinction debt figure
560
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))
561
562
# Panel A: Occupancy trajectory with habitat loss
563
ax1.plot(time_extinction_debt, trajectory_habitat_loss, 'b-', linewidth=2.5, label='Patch occupancy $p(t)$')
564
ax1.axhline(equilibrium_occupancy, color='green', linestyle='--', linewidth=2,
565
label=f'Pre-disturbance equilibrium $p^* = {equilibrium_occupancy:.3f}$')
566
ax1.axhline(new_equilibrium, color='red', linestyle='--', linewidth=2,
567
label=f'Post-loss equilibrium $p^* = {new_equilibrium:.3f}$')
568
ax1.axvline(100, color='orange', linestyle=':', linewidth=2, alpha=0.7,
569
label='Habitat loss begins')
570
ax1.axvline(120, color='orange', linestyle=':', linewidth=2, alpha=0.7)
571
ax1.fill_between([100, 120], 0, 1, alpha=0.1, color='orange', label='Habitat loss period')
572
ax1.set_xlabel('Time (years)', fontsize=12)
573
ax1.set_ylabel('Patch Occupancy Fraction $p(t)$', fontsize=12)
574
ax1.set_title('A) Extinction Debt Following Habitat Loss', fontsize=13, weight='bold')
575
ax1.legend(loc='upper right', fontsize=10)
576
ax1.grid(True, alpha=0.3)
577
ax1.set_xlim(0, 300)
578
ax1.set_ylim(0, 1)
579
580
# Annotate regions
581
ax1.annotate('Stable state', xy=(50, equilibrium_occupancy + 0.05),
582
fontsize=10, ha='center', color='green', weight='bold')
583
ax1.annotate('Transient excess\n(extinction debt)',
584
xy=(180, (trajectory_habitat_loss[1200, 0] + new_equilibrium) / 2),
585
fontsize=10, ha='center', color='red', weight='bold',
586
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
587
588
# Panel B: Extinction debt magnitude over time
589
ax2.fill_between(time_extinction_debt, 0, extinction_debt, alpha=0.4, color='red',
590
label='Extinction debt (excess occupancy)')
591
ax2.plot(time_extinction_debt, extinction_debt, 'r-', linewidth=2)
592
ax2.axvline(100, color='orange', linestyle=':', linewidth=2, alpha=0.7)
593
ax2.axvline(120, color='orange', linestyle=':', linewidth=2, alpha=0.7)
594
ax2.set_xlabel('Time (years)', fontsize=12)
595
ax2.set_ylabel('Extinction Debt\n(Occupancy - Equilibrium)', fontsize=12)
596
ax2.set_title('B) Magnitude of Extinction Debt', fontsize=13, weight='bold')
597
ax2.grid(True, alpha=0.3)
598
ax2.set_xlim(0, 300)
599
ax2.legend(loc='upper right', fontsize=10)
600
601
# Add half-life annotation if calculable
602
if half_life_time < 200:
603
ax2.annotate(f'Half-life: {half_life_time:.1f} years',
604
xy=(120 + half_life_time, extinction_debt[1000 + half_life_idx[0]]),
605
xytext=(180, 0.25), fontsize=10,
606
arrowprops=dict(arrowstyle='->', color='black', lw=1.5),
607
bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.7))
608
609
plt.tight_layout()
610
plt.savefig('metapopulation_extinction_debt.pdf', dpi=300, bbox_inches='tight')
611
plt.close()
612
613
# Calculate debt statistics
614
max_debt = np.max(extinction_debt)
615
debt_at_200yrs = extinction_debt[np.argmin(np.abs(time_extinction_debt - 200))]
616
\end{pycode}
617
618
\begin{figure}[H]
619
\centering
620
\includegraphics[width=0.95\textwidth]{metapopulation_extinction_debt.pdf}
621
\caption{Extinction debt dynamics following habitat loss in a Levins metapopulation. At year 100, habitat destruction begins, reducing colonization rate by 60\% over a 20-year period (orange shaded region). \textbf{Panel A}: Patch occupancy (blue line) declines gradually from the original equilibrium $p^* = \py{f'{equilibrium_occupancy:.3f}'}$ toward the new sustainable level $p^* = \py{f'{new_equilibrium:.3f}'}$. The delayed response creates a transient period where current occupancy exceeds the carrying capacity of the degraded landscape, representing populations "committed to extinction" despite appearing viable. \textbf{Panel B}: The magnitude of extinction debt (red shading) peaks immediately after habitat loss at \py{f'{max_debt:.3f}'} and decays slowly as patches go extinct. After 200 years, an extinction debt of \py{f'{debt_at_200yrs:.3f}'} remains, indicating that habitat loss effects can persist for centuries. This delayed response has critical implications for conservation: species may appear stable for decades after habitat destruction while actually undergoing cryptic decline toward a lower equilibrium.}
622
\end{figure}
623
624
\section{Metapopulation Capacity and Reserve Networks}
625
626
The concept of \textbf{metapopulation capacity} $\lambda_M$ extends the Levins model to realistic landscapes by incorporating spatial structure. The metapopulation capacity is the dominant eigenvalue of the colonization-extinction matrix for the patch network, providing a threshold condition for persistence analogous to $c > e$ in the simple Levins model.
627
628
For persistence, the metapopulation capacity must exceed the extinction rate: $\lambda_M > e$. This allows quantitative assessment of reserve network designs.
629
630
\begin{pycode}
631
# Simulate reserve network design scenarios
632
np.random.seed(123)
633
634
def calculate_metapopulation_capacity(areas, distances, colonization_coef, distance_decay):
635
"""
636
Calculate metapopulation capacity for a patch network
637
638
Based on Hanski & Ovaskainen (2000) formula:
639
Capacity is largest eigenvalue of M where M_ij = A_i^α A_j^β exp(-d_ij/δ)
640
"""
641
n = len(areas)
642
M = np.zeros((n, n))
643
644
for i in range(n):
645
for j in range(n):
646
if i != j:
647
# Colonization from j to i
648
M[i, j] = colonization_coef * areas[i]**0.5 * areas[j]**0.5 * \
649
np.exp(-distance_decay * distances[i, j])
650
651
eigenvalues = np.linalg.eigvals(M)
652
capacity = np.max(np.real(eigenvalues))
653
return capacity
654
655
# Scenario 1: Current reserve network (moderate fragmentation)
656
n_reserves_current = 15
657
reserve_areas_current = np.random.gamma(3, 2, n_reserves_current)
658
659
# Generate distance matrix
660
reserve_positions = np.random.uniform(0, 50, n_reserves_current)
661
distance_matrix_current = np.abs(reserve_positions[:, np.newaxis] - reserve_positions[np.newaxis, :])
662
663
capacity_current = calculate_metapopulation_capacity(reserve_areas_current, distance_matrix_current,
664
colonization_coef=0.15, distance_decay=0.2)
665
666
# Scenario 2: Further fragmentation (smaller, more isolated reserves)
667
reserve_areas_fragmented = reserve_areas_current * 0.5 # halve areas
668
distance_matrix_fragmented = distance_matrix_current * 1.5 # increase isolation
669
670
capacity_fragmented = calculate_metapopulation_capacity(reserve_areas_fragmented,
671
distance_matrix_fragmented,
672
colonization_coef=0.15, distance_decay=0.2)
673
674
# Scenario 3: Adding corridors (reduced effective distance)
675
distance_matrix_corridors = distance_matrix_current * 0.7 # corridors reduce effective distance
676
677
capacity_corridors = calculate_metapopulation_capacity(reserve_areas_current,
678
distance_matrix_corridors,
679
colonization_coef=0.15, distance_decay=0.2)
680
681
# Scenario 4: Larger reserves (double area)
682
reserve_areas_enlarged = reserve_areas_current * 2
683
684
capacity_enlarged = calculate_metapopulation_capacity(reserve_areas_enlarged,
685
distance_matrix_current,
686
colonization_coef=0.15, distance_decay=0.2)
687
688
# Create comparison figure
689
scenarios = ['Current\nNetwork', 'Fragmentation\n(50% area)',
690
'Add Corridors\n(30% closer)', 'Enlarge Reserves\n(2x area)']
691
capacities = [capacity_current, capacity_fragmented, capacity_corridors, capacity_enlarged]
692
colors_scenario = ['gray', 'red', 'green', 'blue']
693
694
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
695
696
# Panel A: Metapopulation capacity comparison
697
bars = ax1.bar(scenarios, capacities, color=colors_scenario, alpha=0.7, edgecolor='black', linewidth=2)
698
ax1.axhline(extinction_rate, color='red', linestyle='--', linewidth=2.5,
699
label=f'Extinction threshold $\\lambda_M = e = {extinction_rate:.2f}$')
700
ax1.set_ylabel('Metapopulation Capacity $\\lambda_M$', fontsize=12)
701
ax1.set_title('A) Reserve Network Scenarios', fontsize=13, weight='bold')
702
ax1.legend(loc='upper left', fontsize=10)
703
ax1.grid(True, alpha=0.3, axis='y')
704
ax1.set_ylim(0, max(capacities) * 1.2)
705
706
# Add viability labels
707
for i, (cap, scenario) in enumerate(zip(capacities, scenarios)):
708
if cap > extinction_rate:
709
label = 'VIABLE'
710
color = 'green'
711
else:
712
label = 'EXTINCT'
713
color = 'red'
714
ax1.text(i, cap + 0.005, label, ha='center', fontsize=9, weight='bold', color=color)
715
716
# Panel B: Reserve network spatial layout (current vs fragmented)
717
ax2_inset = ax2.inset_axes([0.05, 0.55, 0.4, 0.4])
718
719
# Main plot: current network
720
for i in range(n_reserves_current):
721
circle_size = reserve_areas_current[i] * 100
722
ax2.scatter(reserve_positions[i], 1, s=circle_size, c='gray', alpha=0.6,
723
edgecolors='black', linewidth=1.5)
724
725
# Draw connections for strong links
726
for j in range(i+1, n_reserves_current):
727
connection_strength = 0.15 * reserve_areas_current[i]**0.5 * \
728
reserve_areas_current[j]**0.5 * \
729
np.exp(-0.2 * distance_matrix_current[i, j])
730
if connection_strength > 0.05:
731
ax2.plot([reserve_positions[i], reserve_positions[j]], [1, 1],
732
'gray', alpha=0.3, linewidth=connection_strength*30)
733
734
ax2.set_xlim(-5, 55)
735
ax2.set_ylim(0.5, 1.5)
736
ax2.set_xlabel('Spatial Position (km)', fontsize=11)
737
ax2.set_title('B) Current Network Connectivity', fontsize=12, weight='bold')
738
ax2.set_yticks([])
739
ax2.text(25, 1.3, f'$\\lambda_M = {capacity_current:.3f}$', fontsize=12, ha='center',
740
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.7))
741
742
# Inset: fragmented network
743
for i in range(n_reserves_current):
744
circle_size = reserve_areas_fragmented[i] * 100
745
ax2_inset.scatter(reserve_positions[i], 1, s=circle_size, c='red', alpha=0.6,
746
edgecolors='darkred', linewidth=1)
747
748
ax2_inset.set_xlim(-5, 55)
749
ax2_inset.set_ylim(0.5, 1.5)
750
ax2_inset.set_title('Fragmented (smaller, isolated)', fontsize=9)
751
ax2_inset.set_xticks([])
752
ax2_inset.set_yticks([])
753
ax2_inset.text(25, 1.25, f'$\\lambda_M = {capacity_fragmented:.3f}$', fontsize=9, ha='center',
754
bbox=dict(boxstyle='round', facecolor='pink', alpha=0.7))
755
756
plt.tight_layout()
757
plt.savefig('metapopulation_capacity_scenarios.pdf', dpi=300, bbox_inches='tight')
758
plt.close()
759
760
# Calculate relative changes
761
capacity_change_fragmented = ((capacity_fragmented - capacity_current) / capacity_current) * 100
762
capacity_change_corridors = ((capacity_corridors - capacity_current) / capacity_current) * 100
763
capacity_change_enlarged = ((capacity_enlarged - capacity_current) / capacity_current) * 100
764
\end{pycode}
765
766
\begin{figure}[H]
767
\centering
768
\includegraphics[width=0.98\textwidth]{metapopulation_capacity_scenarios.pdf}
769
\caption{Metapopulation capacity ($\lambda_M$) for alternative reserve network designs. \textbf{Panel A}: Comparison of four management scenarios showing how landscape configuration affects viability. The current network maintains $\lambda_M = \py{f'{capacity_current:.3f}'}$ above the extinction threshold ($e = \py{extinction_rate}$), ensuring metapopulation persistence. Fragmenting reserves by reducing area 50\% causes a \py{f'{abs(capacity_change_fragmented):.1f}'}\% decline to $\lambda_M = \py{f'{capacity_fragmented:.3f}'}$, potentially pushing the metapopulation below viability. Adding habitat corridors that reduce effective isolation by 30\% increases capacity by \py{f'{capacity_change_corridors:.1f}'}\% to $\lambda_M = \py{f'{capacity_corridors:.3f}'}$, while doubling reserve areas yields a \py{f'{capacity_change_enlarged:.1f}'}\% increase to $\lambda_M = \py{f'{capacity_enlarged:.3f}'}$. \textbf{Panel B}: Spatial configuration of the current reserve network (circle size proportional to area, gray lines show strong connectivity). Inset shows the fragmented scenario with smaller, more isolated patches and reduced connectivity. This demonstrates that metapopulation viability depends on the entire landscape configuration, not just total habitat area, with corridors and patch size having quantifiable effects on persistence probability.}
770
\end{figure}
771
772
\section{Results Summary}
773
774
Our computational analysis of metapopulation dynamics reveals fundamental principles governing population persistence in fragmented landscapes:
775
776
\begin{enumerate}
777
\item \textbf{Colonization-Extinction Balance}: The Levins model equilibrium $p^* = 1 - e/c$ demonstrates that metapopulation persistence requires colonization rate ($c = \py{colonization_rate}$) to exceed extinction rate ($e = \py{extinction_rate}$), yielding equilibrium occupancy of \py{f'{equilibrium_occupancy:.3f}'} for our parameter set.
778
779
\item \textbf{Extinction Threshold}: Metapopulations face an abrupt transition to extinction when the colonization-extinction ratio falls below unity ($c/e < 1$). Our current system maintains $c/e = \py{f'{critical_ratio:.2f}'}$, well above the critical threshold.
780
781
\item \textbf{Patch Heterogeneity}: In realistic landscapes with heterogeneous patch sizes and isolation, incidence functions show that \py{f'{viable_patch_fraction*100:.1f}'}\% of patches support viable populations, with mean equilibrium occupancy of \py{f'{mean_incidence:.3f}'} for occupied patches.
782
783
\item \textbf{Source-Sink Dynamics}: High-quality source patches (mean occupancy \py{f'{source_occupancy:.3f}'}) subsidize demographic sinks (mean occupancy \py{f'{sink_occupancy:.3f}'}), demonstrating that regional persistence can exceed predictions based on local population viability alone.
784
785
\item \textbf{Extinction Debt}: Following 60\% habitat loss, the metapopulation carries an extinction debt that peaks at \py{f'{max_debt:.3f}'} excess occupancy and persists for centuries (debt of \py{f'{debt_at_200yrs:.3f}'} after 200 years), indicating delayed responses to environmental change.
786
787
\item \textbf{Metapopulation Capacity}: Reserve network design critically affects viability through metapopulation capacity $\lambda_M$. Fragmenting the current network reduces capacity by \py{f'{abs(capacity_change_fragmented):.1f}'}\%, while corridors increase it by \py{f'{capacity_change_corridors:.1f}'}\% and larger reserves by \py{f'{capacity_change_enlarged:.1f}'}\%.
788
\end{enumerate}
789
790
\begin{pycode}
791
# Generate summary table
792
print(r'\begin{table}[H]')
793
print(r'\centering')
794
print(r'\caption{Key Metapopulation Dynamics Parameters and Results}')
795
print(r'\begin{tabular}{@{}lcc@{}}')
796
print(r'\toprule')
797
print(r'\textbf{Parameter/Result} & \textbf{Value} & \textbf{Interpretation} \\')
798
print(r'\midrule')
799
print(f"Colonization rate $c$ & {colonization_rate:.3f} yr$^{{-1}}$ & Patch colonization coefficient \\\\")
800
print(f"Extinction rate $e$ & {extinction_rate:.3f} yr$^{{-1}}$ & Local extinction probability \\\\")
801
print(f"Equilibrium occupancy $p^*$ & {equilibrium_occupancy:.3f} & Levins model equilibrium \\\\")
802
print(f"$c/e$ ratio & {critical_ratio:.2f} & Exceeds threshold (viable) \\\\")
803
print(r'\midrule')
804
print(f"Viable patch fraction & {viable_patch_fraction*100:.1f}\\% & Heterogeneous landscape \\\\")
805
print(f"Source patch occupancy & {source_occupancy:.3f} & High-quality patches \\\\")
806
print(f"Sink patch occupancy & {sink_occupancy:.3f} & Immigration-dependent \\\\")
807
print(r'\midrule')
808
print(f"Max extinction debt & {max_debt:.3f} & Post-habitat loss \\\\")
809
print(f"Debt at 200 years & {debt_at_200yrs:.3f} & Long-term legacy \\\\")
810
print(r'\midrule')
811
print(f"Current $\\lambda_M$ & {capacity_current:.3f} & Viable network \\\\")
812
print(f"Fragmented $\\lambda_M$ & {capacity_fragmented:.3f} & Reduced viability \\\\")
813
print(f"Corridor effect & +{capacity_change_corridors:.1f}\\% & Improved connectivity \\\\")
814
print(r'\bottomrule')
815
print(r'\end{tabular}')
816
print(r'\end{table}')
817
\end{pycode}
818
819
\section{Conclusions}
820
821
This computational analysis demonstrates that metapopulation persistence in fragmented landscapes depends fundamentally on the balance between colonization and extinction at the patch scale. The classical Levins model reveals that regional persistence requires colonization rates to exceed extinction rates ($c > e$), yielding an equilibrium occupancy of $p^* = \py{f'{equilibrium_occupancy:.3f}'}$ for our parameter set with $c = \py{colonization_rate}$ and $e = \py{extinction_rate}$. This threshold structure implies that habitat loss reducing colonization rates can trigger abrupt metapopulation collapse when the colonization-extinction ratio falls below unity, even when substantial habitat remains.
822
823
Extensions to heterogeneous landscapes reveal that patch-specific characteristics create incidence functions where only \py{f'{viable_patch_fraction*100:.1f}'}\% of patches maintain positive equilibrium occupancy. Source-sink dynamics demonstrate that high-quality patches (mean occupancy \py{f'{source_occupancy:.3f}'}) can subsidize populations in demographic sinks (occupancy \py{f'{sink_occupancy:.3f}'}), extending metapopulation range beyond areas capable of supporting self-sustaining populations. However, this spatial subsidy depends on maintained connectivity, which habitat fragmentation systematically erodes.
824
825
Perhaps most critically for conservation, the extinction debt phenomenon shows that metapopulation responses to habitat loss are delayed, not instantaneous. Following 60\% habitat destruction in our simulation, excess occupancy peaked at \py{f'{max_debt:.3f}'} and persisted for centuries (debt \py{f'{debt_at_200yrs:.3f}'} after 200 years), indicating that species may appear stable for decades while undergoing cryptic decline toward lower equilibria. This temporal lag complicates conservation assessment and may lead to systematic underestimation of extinction risk.
826
827
The metapopulation capacity framework provides a quantitative tool for reserve network design, showing that current configuration maintains $\lambda_M = \py{f'{capacity_current:.3f}'}$ above the extinction threshold. However, further fragmentation reducing patch areas by 50\% would decrease capacity by \py{f'{abs(capacity_change_fragmented):.1f}'}\%, potentially pushing the system below viability. Conversely, habitat corridors reducing effective isolation increase capacity by \py{f'{capacity_change_corridors:.1f}'}\%, demonstrating that connectivity restoration can be as effective as habitat expansion for metapopulation conservation.
828
829
These results underscore that persistence in fragmented landscapes requires not just sufficient total habitat area, but appropriate spatial configuration maintaining colonization-extinction balance across patch networks. Conservation strategies must account for metapopulation dynamics when designing reserves, prioritizing both patch size (to reduce extinction rates) and connectivity (to maintain colonization rates) to ensure long-term population viability.
830
831
\bibliographystyle{ecology}
832
\begin{thebibliography}{99}
833
834
\bibitem{levins1969some}
835
Levins, R. (1969). Some demographic and genetic consequences of environmental heterogeneity for biological control. \textit{Bulletin of the Entomological Society of America}, 15(3), 237--240.
836
837
\bibitem{hanski1999metapopulation}
838
Hanski, I. (1999). \textit{Metapopulation Ecology}. Oxford University Press, Oxford.
839
840
\bibitem{fahrig2003effects}
841
Fahrig, L. (2003). Effects of habitat fragmentation on biodiversity. \textit{Annual Review of Ecology, Evolution, and Systematics}, 34, 487--515.
842
843
\bibitem{hanski1998metapopulation}
844
Hanski, I. (1998). Metapopulation dynamics. \textit{Nature}, 396(6706), 41--49.
845
846
\bibitem{hanski2000metapopulation}
847
Hanski, I., \& Ovaskainen, O. (2000). The metapopulation capacity of a fragmented landscape. \textit{Nature}, 404(6779), 755--758.
848
849
\bibitem{tilman1994habitat}
850
Tilman, D., May, R. M., Lehman, C. L., \& Nowak, M. A. (1994). Habitat destruction and the extinction debt. \textit{Nature}, 371(6492), 65--66.
851
852
\bibitem{pulliam1988sources}
853
Pulliam, H. R. (1988). Sources, sinks, and population regulation. \textit{American Naturalist}, 132(5), 652--661.
854
855
\bibitem{brown2001turnover}
856
Brown, J. H., \& Kodric-Brown, A. (1977). Turnover rates in insular biogeography: effect of immigration on extinction. \textit{Ecology}, 58(2), 445--449.
857
858
\bibitem{harrison1991local}
859
Harrison, S. (1991). Local extinction in a metapopulation context: an empirical evaluation. \textit{Biological Journal of the Linnean Society}, 42(1-2), 73--88.
860
861
\bibitem{hanski1994practical}
862
Hanski, I., Pakkala, T., Kuussaari, M., \& Lei, G. (1995). Metapopulation persistence of an endangered butterfly in a fragmented landscape. \textit{Oikos}, 72(1), 21--28.
863
864
\bibitem{sjogren1991extinction}
865
Sj\"ogren-Gulve, P. (1994). Distribution and extinction patterns within a northern metapopulation of the pool frog, \textit{Rana lessonae}. \textit{Ecology}, 75(5), 1357--1367.
866
867
\bibitem{moilanen1998metapopulation}
868
Moilanen, A. (1999). Patch occupancy models of metapopulation dynamics: efficient parameter estimation using implicit statistical inference. \textit{Ecology}, 80(3), 1031--1043.
869
870
\bibitem{etienne2004estimating}
871
Etienne, R. S., ter Braak, C. J., \& Vos, C. C. (2004). Application of stochastic patch occupancy models to real metapopulations. In I. Hanski \& O. E. Gaggiotti (Eds.), \textit{Ecology, Genetics and Evolution of Metapopulations} (pp. 105--132). Academic Press.
872
873
\bibitem{ovaskainen2002metapopulation}
874
Ovaskainen, O., \& Hanski, I. (2002). Transient dynamics in metapopulation response to perturbation. \textit{Theoretical Population Biology}, 61(3), 285--295.
875
876
\bibitem{wahlberg2002metapopulation}
877
Wahlberg, N., Klemetti, T., \& Hanski, I. (2002). Dynamic populations in a dynamic landscape: the metapopulation structure of the marsh fritillary butterfly. \textit{Ecography}, 25(2), 224--232.
878
879
\bibitem{clinchy2002predator}
880
Clinchy, M., Haydon, D. T., \& Smith, A. T. (2002). Pattern does not equal process: what does patch occupancy really tell us about metapopulation dynamics? \textit{The American Naturalist}, 159(4), 351--362.
881
882
\bibitem{keyghobadi2005metapopulation}
883
Keyghobadi, N., Roland, J., \& Strobeck, C. (2005). Genetic differentiation and gene flow among populations of the alpine butterfly, \textit{Parnassius smintheus}, vary with landscape connectivity. \textit{Molecular Ecology}, 14(7), 1897--1909.
884
885
\bibitem{matter2003determinants}
886
Matter, S. F. (2003). Determinants of local extinction and colonization in a metapopulation of the checkered white butterfly \textit{Pontia protodice}. \textit{Oecologia}, 135(2), 151--156.
887
888
\bibitem{kindvall1996habitat}
889
Kindvall, O. (1996). Habitat heterogeneity and survival in a bush cricket metapopulation. \textit{Ecology}, 77(1), 207--214.
890
891
\bibitem{fleishman2002influence}
892
Fleishman, E., Ray, C., Sj\"ogren-Gulve, P., Boggs, C. L., \& Murphy, D. D. (2002). Assessing the roles of patch quality, area, and isolation in predicting metapopulation dynamics. \textit{Conservation Biology}, 16(3), 706--716.
893
894
\end{thebibliography}
895
896
\end{document}
897
898