21  Spatial Structure

openMSE supports spatially structured operating models with an arbitrary number of areas. Fish move between areas according to a movement probability matrix, and fishing effort can be distributed across areas either dynamically or according to a fixed allocation (see Section 21.8). Management can be applied differentially by area through spatial closures, and area-specific selectivity and retention.

The Spatial subcomponent of a Stock object defines the spatial structure. It is optional; if omitted the model assumes a single well-mixed area with no spatial dynamics.

This chapter describes the available approaches for specifying spatial structure, from the simplest two-area case through to models with age-varying movement and three or more areas. All Stock objects within an operating model must share the same spatial configuration (i.e., the same number of areas).

21.1 Single-Area Models

For a non-spatial model, simply omit the Spatial subcomponent entirely. Populate() will create a default single-area structure automatically:

# No Spatial subcomponent: single well-mixed area assumed
stock <- Stock(Name = "My Stock")
# Spatial(stock) is NULL; Populate() will assign a single-area default

21.2 Two-Area Models

Two-area models are a common spatial configuration. They require three inputs: the unfished biomass distribution across areas, the probability of remaining in an area, and the relative size of each area.

For two-area models, UnfishedDist and RelativeSize each specify Area 1 only; the corresponding Area 2 values are derived as 1 - x. Similarly, ProbStaying specifies Area 1 only; the Area 2 staying probability is solved internally so that the asymptotic movement equilibrium reproduces the target UnfishedDist.

Each parameter accepts a scalar (constant across all simulations), a length-2 bounds vector (sampled from Uniform(lower, upper) independently for each simulation), or a full Sim × Area × Age × Year array:

# Stochastic samples from uniform distribution
Spatial(stock) <- Spatial(
  UnfishedDist = c(0.2, 0.4),   # fraction of unfished biomass in Area 1
  ProbStaying  = c(0.6, 0.8),   # probability of staying in Area 1
  RelativeSize = c(0.3, 0.5)    # relative habitat size of Area 1
)
# Fixed — same across all simulations
Spatial(stock) <- Spatial(
  UnfishedDist = 0.3,
  ProbStaying  = 0.7,
  RelativeSize = 0.4
)

21.3 Relative Area Size

RelativeSize controls how biomass is converted to density for the purpose of spatial effort allocation. Fleets are attracted to areas with high biomass density (biomass per unit area), so RelativeSize affects where effort concentrates even when biomass is held constant.

Three options are available:

21.3.1 Fixed proportion

For two-area models, a scalar or bounds vector gives the fraction of total habitat in Area 1, with Area 2 taken as 1 - RelativeSize:

# Area 1 is 40% of total habitat
RelativeSize = 0.4   

For models with three or more areas, this shortcut doesn’t apply; RelativeSize must instead be supplied as a vector of length nArea giving every area’s share directly, summing to 1:

# Areas 1-3 are 10%, 40%, 50% of total habitat
RelativeSize = c(0.1, 0.4, 0.5)   

21.3.2 Equal density

Setting RelativeSize = "EqualDensity" sets each area’s relative size equal to its mean unfished biomass share across ages and years. This ensures that unfished biomass density is equal across areas, regardless of the number of areas:

Spatial(stock) <- Spatial(
  UnfishedDist = 0.3,
  ProbStaying  = 0.7,
  RelativeSize = "EqualDensity"
)

21.3.3 Equal size

When RelativeSize is omitted (NULL), all areas are assumed equal in size (1 / nArea each), whatever the number of areas.

21.4 How the Movement Matrix is Derived

When UnfishedDist and ProbStaying are supplied, Populate() derives the movement probability matrix by numerical optimisation. For each simulation, age, and year, the matrix diagonal (staying probabilities) and off-diagonal elements (movement probabilities) are fitted to simultaneously satisfy:

  1. The asymptotic distribution implied by the movement matrix matches the target UnfishedDist.
  2. The diagonal elements match the target ProbStaying.

For two-area models this system has a closed-form solution. For models with three or more areas, a penalty-based optimisation is used (see Section 21.6). The full mathematical specification of both cases is given in Section 27.7.

The resulting Movement array has dimensions Sim × FromArea × ToArea × Age × Year. Row [i, ] gives the probabilities of moving from area i to each other area, and rows sum to 1. See Section 27.6 for how the resulting movement matrix is applied within the historical population dynamics.

21.5 Age-Varying Movement

Movement rates can vary by age by supplying UnfishedDist and/or ProbStaying as arrays with an Age dimension. A separate movement matrix is fitted for each age class: the matrix for age a is fit so that applying it to age a’s target unfished distribution reproduces age a + 1’s target distribution, chaining the age-specific matrices so the full age-specific distribution profile is realised as fish age through the model. The plus group, and any model where UnfishedDist doesn’t vary by age, fits to the matrix’s own asymptotic distribution instead, since the same matrix is reapplied every time step.

An example application is ontogenetic habitat shift, where juveniles and adults occupy different areas:

ages <- Ages(MaxAge = 10)
nage <- nAge(ages)

# Juveniles concentrate in Area 1; adults spread to Area 2
area1_frac <- seq(0.8, 0.2, length.out = nage)

ud <- array(
  c(area1_frac, 1 - area1_frac),
  dim      = c(1, 2, nage),
  dimnames = list(Sim = 1, Area = 1:2, Age = Classes(ages))
)

Spatial(stock) <- Spatial(
  UnfishedDist = ud,
  ProbStaying  = 0.9
)

21.6 Multi-Area Models

Models with three or more areas require UnfishedDist to be supplied as an array summing to 1 across the Area dimension, and additionally require FracOther to guide the movement optimisation.

FracOther is a Sim × FromArea × ToArea array (optionally extended with Age and Year dimensions) where off-diagonal element [i, j] gives the relative probability of moving from area i to area j. Diagonal elements must be NA as the staying probability is taken from ProbStaying. The values in FracOther should sum to 1, but are normalised internally so it is not essential.

nArea <- 3

# Unfished biomass: 50% in Area 1, 20% in Area 2, 30% in Area 3
ud <- matrix(c(0.5, 0.2, 0.3), nrow = 1, ncol = nArea)

# FracOther: relative movement probabilities between areas
# Area 1 mostly connects to Area 2, weakly to Area 3
# Area 2 connects equally to Areas 1 and 3
# Area 3 mostly connects to Area 2, weakly to Area 1
fo <- array(NA, dim = c(1, nArea, nArea))
fo[1, 1, ] <- c(NA,  1,   0.1)
fo[1, 2, ] <- c(1,   NA,  1  )
fo[1, 3, ] <- c(0.1, 1,   NA )

Spatial(stock) <- Spatial(
  UnfishedDist = ud,
  ProbStaying  = c(0.9, 0.2, 0.9),
  FracOther    = fo,
  RelativeSize = c(0.1, 0.4, 0.5)
)

See Section 27.7 for the technical details regarding how the movement matrix is calculated.

21.7 Supplying a Movement Matrix Directly

As an alternative to specifying UnfishedDist and ProbStaying and letting Populate() derive the movement matrix, the movement matrix can be supplied directly. This is useful when the matrix has been estimated externally (e.g., from a tagging study or a spatially-explicit model).

The matrix must have dimensions Sim × FromArea × ToArea, optionally extended to Sim × FromArea × ToArea × Age × Year. When Movement is supplied, the asymptotic unfished distribution is back-calculated from the matrix and assigned to UnfishedDist, overwriting any values already present:

mov <- array(
  c(0.7, 0.3,
    0.2, 0.8),
  dim      = c(1, 2, 2),
  dimnames = list(Sim = 1, FromArea = 1:2, ToArea = 1:2)
)

Spatial(stock) <- Spatial(Movement = mov)

21.8 Spatial Effort Allocation

Spatial structure in the Stock object defines where fish are. How fishing effort is distributed across areas is controlled by the Effort subcomponent of each Fleet object, specifically the Distribution and Targeting slots.

When Distribution is not specified, effort is allocated dynamically each year based on relative exploitable biomass density across areas, with the degree of concentration controlled by the Targeting parameter. A value of 0 produces uniform effort allocation regardless of biomass distribution; larger values concentrate effort more strongly in the highest-density area. The default Targeting value is 0.8.

Users can fix effort to a specific spatial allocation by supplying Distribution as a Sim × Year × Area array, bypassing the dynamic allocation algorithm entirely. Any cells set to a non-NA value are treated as fixed overrides and are not modified.

See ?Effort for full documentation of Distribution and Targeting, and Section 27.2 for the mathematical specification of the spatial effort allocation algorithm.