Skip to content

Optimization

SeapoPym provides built-in parameter calibration through four optimization algorithms. Because the simulation is end-to-end JAX, gradient-based methods can differentiate through the entire model, while evolutionary strategies offer gradient-free alternatives.

Overview

Optimizer Algorithm Gradients Library Best For
GradientOptimizer Adam, SGD, RMSProp Yes (autodiff) Optax Smooth, local optimization
CMAESOptimizer CMA-ES No evosax Noisy, non-smooth landscapes
GAOptimizer Genetic Algorithm No evosax Discontinuous landscapes
IPOPCMAESOptimizer IPOP-CMA-ES No evosax Multimodal (finding multiple solutions)

Quick Example

from seapopym.optimization import GAOptimizer, Objective
import xarray as xr

# Define what to compare against
objective = Objective(
    observations=observed_biomass,  # xr.DataArray or pd.DataFrame
    target="biomass",               # extract this variable from model outputs
)

# Set parameter bounds
bounds = {
    "lambda_0": (1e-8, 1e-5),
    "gamma_lambda": (0.01, 0.5),
    "tau_r_0": (1e6, 1e8),
}

# Create and run optimizer
optimizer = GAOptimizer(
    objectives=[(objective, "rmse", 1.0)],
    bounds=bounds,
    popsize=64,
)
result = optimizer.run(model, n_generations=200, patience=50)

print(f"Best loss: {result.loss:.6f}")
print(f"Converged: {result.converged} ({result.n_iterations} iterations)")
print(f"Best parameters: {result.params}")

Objectives

An Objective defines what model outputs to compare against observations.

Target Mode (Auto-Extraction)

Extract a variable by name from model outputs:

objective = Objective(
    observations=obs_data,   # xr.DataArray with matching coords
    target="biomass",        # variable name in model outputs
)

The optimizer automatically handles coordinate alignment — observations don't need to cover the full model grid.

Transform Mode (Custom Extraction)

Define a custom function to derive predictions from outputs:

def extract_surface_biomass(outputs):
    """Sum biomass over functional groups, select surface layer."""
    return outputs["biomass"].sum(axis=0)  # sum over F dimension

objective = Objective(
    observations=obs_data,
    transform=extract_surface_biomass,
)

Loss Functions

Three built-in metrics:

Metric Formula Properties
"rmse" \(\sqrt{\text{mean}((y - \hat{y})^2)}\) Same units as data
"nrmse" \(\text{RMSE} / \sigma_y\) Dimensionless, comparable across variables
"mse" \(\text{mean}((y - \hat{y})^2)\) More stable gradients than RMSE

All metrics support sparse observations via masking — only grid points with valid observations contribute to the loss.

Weighted Multi-Objective

Combine multiple objectives with weights:

optimizer = GAOptimizer(
    objectives=[
        (biomass_objective, "rmse", 1.0),
        (production_objective, "nrmse", 0.5),
    ],
    bounds=bounds,
)

Total loss: \(\mathcal{L} = \sum_i w_i \cdot \text{metric}_i(y_i, \hat{y}_i) + \text{prior penalty}\)

Priors

Priors add regularization and define parameter bounds for evolutionary strategies:

from seapopym.optimization import Uniform, Normal, LogNormal, PriorSet

priors = PriorSet({
    "lambda_0": LogNormal(mu=-15, sigma=1.0),     # positive, log-scale
    "gamma_lambda": Normal(loc=0.1, scale=0.05),   # centered around 0.1
    "tau_r_0": Uniform(low=1e6, high=1e8),          # flat prior
})
Prior Use Case Bounds
Uniform(low, high) Hard bounds, no preference [low, high]
Normal(loc, scale) Soft centering around expected value From quantiles
LogNormal(mu, sigma) Positive parameters, log-scale From quantiles
HalfNormal(scale) Positive parameters, zero-centered [0, quantile]
TruncatedNormal(loc, scale, low, high) Soft shape with hard bounds [low, high]

Gradient Optimizer

Uses Optax for gradient-based optimization. The entire simulation is differentiated via jax.value_and_grad.

from seapopym.optimization import GradientOptimizer

optimizer = GradientOptimizer(
    objectives=[(objective, "mse", 1.0)],
    bounds=bounds,
    algorithm="adam",
    learning_rate=1e-3,
    scaling="bounds",       # normalize params to [0,1]
)
result = optimizer.run(model, n_steps=500, tolerance=1e-6)

Scaling options:

Mode Description
"none" Raw parameter values
"bounds" Normalize to [0, 1] using bounds
"log" Log-space optimization (positive params)

CMA-ES Optimizer

Covariance Matrix Adaptation Evolution Strategy — a population-based optimizer that learns the search distribution.

from seapopym.optimization import CMAESOptimizer

optimizer = CMAESOptimizer(
    objectives=[(objective, "rmse", 1.0)],
    bounds=bounds,
    popsize=32,
    seed=42,
)
result = optimizer.run(model, n_generations=200, patience=50)

When to use CMA-ES

CMA-ES excels when the loss landscape is noisy or non-smooth. It requires no gradient information and adapts its search covariance to the local geometry.

Genetic Algorithm

Simple evolutionary strategy with crossover and mutation.

from seapopym.optimization import GAOptimizer

optimizer = GAOptimizer(
    objectives=[(objective, "rmse", 1.0)],
    bounds=bounds,
    popsize=64,
    seed=42,
)
result = optimizer.run(model, n_generations=250, patience=50, progress_bar=True)

IPOP-CMA-ES

Increasing Population CMA-ES — runs multiple CMA-ES restarts with doubling population size. Designed to find multiple local optima.

from seapopym.optimization import IPOPCMAESOptimizer

optimizer = IPOPCMAESOptimizer(
    objectives=[(objective, "rmse", 1.0)],
    bounds=bounds,
    initial_popsize=8,
    n_restarts=8,
    n_generations=100,
    distance_threshold=0.1,  # min distance between distinct modes
    seed=42,
)
result = optimizer.run(model)

# result.modes contains distinct solutions sorted by loss
for i, mode in enumerate(result.modes):
    print(f"Mode {i}: loss={mode.loss:.6f}")

OptimizeResult

All optimizers return an OptimizeResult:

Attribute Type Description
params dict[str, Array] Best parameter values found
loss float Final loss value
loss_history list[float] Loss at each iteration/generation
n_iterations int Number of steps performed
converged bool Whether convergence criterion was met
message str Status description

IPOP-CMA-ES returns an IPOPResult with additional:

Attribute Type Description
modes list[OptimizeResult] Distinct solutions, sorted by loss
all_results list[OptimizeResult] All restart results
n_restarts int Total restarts performed