Module field_inversion
Brief summary
field_inversion is IGM's newer inversion route, set to replace
data_assimilation in the long term. It inverts for unknown fields (e.g. ice
thickness) from surface
observations (e.g. observed surface ice velocities) by iteratively minimising a
cost function that penalises the misfit between model predictions and observations
plus a regularization penalty on the control variables. Optionally, the iceflow
network emulator can be fine-tuned between inversion iterations to keep it
self-consistent with the updated geometry.
The parameters of the module are described here.
State variables
Reads: usurf, thk, icemask, uvelsurfobs, vvelsurfobs
Writes: —
Overview
field_inversion uses gradient-based optimisation to recover one or more control variables (e.g. thk, tau_ref, arrhenius) from surface velocity observations. Each call to update performs the following sequence:
-
DA phase — minimise the total cost \(J(\boldsymbol{\theta})\) (defined below) with respect to the control variables using an L-BFGS optimiser with optional box constraints. Snapshots of the cost and the state fields are written to
optimize.nceveryoutput.freqaccepted iterations. -
Retraining phases (optional, repeated
optimization.retrain_itertimes) — after each DA phase, fine-tune the pretrained iceflow network emulator on the current glacier geometry, then run another DA phase. This alternation keeps the emulator consistent with the inverted fields as they evolve. Care must be taken to avoid retraining too aggressively however, as this can lead to overfitting and loss of generalization.
The control variables are declared in the variables list, and the cost function is assembled term by term from the objective dictionary. The default configuration inverts for thk using a surface-velocity Gaussian misfit against uvelsurfobs/vvelsurfobs and a Laplacian-based smoothness regularization on thk.
Control variables (variables)
variables is a list of dictionaries, one per field to invert. Each entry accepts the following keys:
| Key | Required | Default | Meaning |
|---|---|---|---|
name |
yes | — | Name of the state field to invert (e.g. thk, tau_ref, arrhenius). |
transform |
no | identity |
Parameterization used by the optimiser (see below). |
lower_bound |
no | unbounded | Lower box constraint, in physical units (e.g. metres for thk). |
upper_bound |
no | unbounded | Upper box constraint, in physical units. |
mask |
no | icemask |
Name of a state field used as a boolean mask; only cells where the mask is non-zero are optimised, all other cells keep their initial values. Dotted paths (e.g. iceflow.some_mask) are resolved on state. |
Duplicate variable names are rejected, the mask must have the same shape as the field, and a mask with no active cells is an error.
Which names can be inverted? Any field present on state can be declared, but a control variable only influences the modelled velocities if it is one of the iceflow emulator's input channels (processes.iceflow.unified.inputs — by default thk, usurf, arrhenius, tau_ref, dX). In practice the supported control variables are:
| Name | Physical meaning | Initialisation at startup |
|---|---|---|
thk |
Ice thickness (m) | Estimated from observations (see below), unless use_existing_thk: true, in which case the existing state.thk is used. |
tau_ref |
Sliding reference stress (see iceflow sliding physics) |
Constant field set to processes.iceflow.physics.sliding.tau_ref. |
arrhenius |
Arrhenius (rate) factor (see iceflow viscosity physics) |
Constant field set to processes.iceflow.physics.viscosity.arrhenius. |
usurf |
Surface elevation (m) | Taken from the existing state.usurf. |
Initial thickness construction — when thk is a control variable (and use_existing_thk is false), the module initialises it from a blend of an SIA-based thickness estimate (reliable where speeds are high) and a distance-from-margin estimate (reliable near the margin), blended smoothly by surface speed magnitude, then smoothed and clamped.
Transforms
The optimiser works on an internal variable \(\theta\) related to the physical field \(x\) by the chosen transform. Bounds are always given in physical space and converted automatically. Available transforms:
transform |
Mapping | When to use |
|---|---|---|
identity |
\(x = \theta\) | Default; fields whose natural scale is well-behaved (e.g. thk). |
log10 |
\(x = 10^{\theta}\) | Positive fields spanning orders of magnitude (e.g. arrhenius, tau_ref); guarantees positivity. |
softplus |
\(x = \log(1 + e^{\theta})\) | Positive fields; smooth alternative to log10 that behaves linearly for large values. |
mpa_to_kpa_theta |
\(x\,[\mathrm{MPa}] = \theta / 1000\) | Optimise a stress in kPa while the physical parameter stays in MPa (pure rescaling for better optimiser conditioning). |
Example inverting simultaneously for thickness and sliding:
variables:
- { name: thk, transform: identity, lower_bound: 0.0, upper_bound: 1000.0, mask: icemask }
- { name: tau_ref, transform: log10, lower_bound: 0.001, upper_bound: 1.0 }
Simultaneous inversions
Simultaneous inversions are largely untested and more work is needed to ensure robustness and convergence. For now we recommend only inverting for one unknown field at a time.
Objective function (objective)
The total cost is the sum of all misfit and regularization terms declared in objective:
Misfit terms (objective.misfit)
objective.misfit is a list of dictionaries. Each entry compares one or more modelled quantities against observation fields on state:
| Key | Required | Default | Meaning |
|---|---|---|---|
name |
yes | — | Label for the term (appears in logs as misfit:<name>). |
kind |
no | gaussian |
Error model: gaussian or huber. |
components |
no | [<name>] |
Modelled quantities to compare. Available: uvelsurf, vvelsurf (surface velocities from the emulator), divflux (flux divergence from depth-averaged velocities and thk), or the name of any control variable. |
obs |
yes | — | Observation fields on state, one per component (lengths must match). |
std |
yes | — | Observation standard deviation \(\sigma\), in the same units as the observations; residuals are scaled by \(1/\sigma\). |
mask |
no | icemask |
State field restricting where the misfit is evaluated. |
delta |
no | 1.0 |
Huber transition point in \(\sigma\)-units (huber only; ignored by gaussian). |
eps |
no | 1e-12 |
Numerical guard added to denominators. |
Cells where any observation component is NaN are excluded from the mask automatically — NaN means "no data".
Gaussian (kind: gaussian) — a standard least-squares misfit. With model components \(m_k\), observations \(y_k\), and shared standard deviation \(\sigma\):
Huber (kind: huber) — a robust misfit applied to the residual norm (isotropic in component space, not per-component). With \(r = \sqrt{\sum_k \left((y_k - m_k)/\sigma\right)^2 + \epsilon}\) and transition \(\delta\):
The Huber misfit is quadratic for residuals below \(\delta\) (in \(\sigma\)-units) and linear beyond, capping each pixel's gradient contribution at \(\delta\) so observational outliers cannot dominate the inversion. It reduces to the Gaussian misfit as \(\delta \to \infty\).
Regularization terms (objective.regularization)
objective.regularization is a list of dictionaries. Each entry applies a penalty to one control variable:
| Key | Required | Default | Meaning |
|---|---|---|---|
name |
yes | — | Control variable to penalise (must be declared in variables). |
penalty |
yes | — | Penalty type: squared_laplacian or l2. |
lam |
yes | — | Penalty weight \(\lambda\). |
mask |
no | full domain | State field restricting where the penalty is evaluated (note: unlike misfits, the default is the whole grid, not icemask). |
ref / prior |
no | none | State field \(\theta_\text{ref}\) to penalise deviations from; without it the field itself is penalised. Cells where the reference is NaN are excluded. |
eps |
no | 1e-12 |
Numerical guard. |
Squared Laplacian (penalty: squared_laplacian) — penalises curvature of the field (or of its deviation from the reference), promoting smoothness:
where \(\nabla^2_m\) is a 5-point-stencil Laplacian that uses only neighbours inside the mask, so values outside the mask never influence the penalty (zero-flux behaviour at the mask boundary). If no ref is given, \(\theta_\text{ref} = 0\) and the field itself is smoothed.
L2 (penalty: l2) — penalises the magnitude of the field, or its distance to a prior:
With ref set this is a classical background/prior term pulling the solution toward \(\theta_\text{ref}\); without it, it shrinks the field toward zero.
At least one misfit or regularization term must be defined; an empty objective is an error.
Example — velocity misfit plus a flux-divergence constraint, smoothness on thk, and a prior on tau_ref:
objective:
misfit:
- { name: velsurf, kind: huber, delta: 2.0, components: [uvelsurf, vvelsurf],
obs: [uvelsurfobs, vvelsurfobs], std: 1.0 }
- { name: divflux, kind: gaussian, components: [divflux],
obs: [divfluxobs], std: 0.5 }
regularization:
- { name: thk, penalty: squared_laplacian, lam: 100000.0 }
Retraining losses
When optimization.retrain_iter > 0, each retraining phase fine-tunes the emulator by minimising a weighted combination of:
- Local physics loss — energy-functional residual on the current glacier inputs.
- Anchor loss (
retrain_anchor_weight) — L2 distance between the current network weights and a snapshot of the pretrained weights, preventing large drift. - Replay data loss (
retrain_replay_data_weight) — supervised loss against a TFRecord dataset of reference velocity examples (requiresreplay_data_dir). - Replay physics loss (
retrain_replay_phys_weight) — physics residual evaluated on the replay samples.
Loss scales for the local and replay terms are normalised at the start of each retraining phase to prevent any single term from dominating.
Replay data
Note that replay terms are still being tested and the required tfrecord files used to train the offline emulator not yet available on the public version of IGM, pending publication.
Module ordering
field_inversion must appear in the assimilations list. It initialises the iceflow forward model internally at startup (before the normal processes pass), so iceflow must also be present in processes.
Inputs
The module reads the following state fields at each call (they must have been initialised by other modules or loaded from file before field_inversion.update is called):
| Field | Description |
|---|---|
usurf |
Surface elevation (m) |
thk |
Ice thickness (m) — also a control variable in the default config |
icemask |
Ice extent mask (1 = ice, 0 = no ice) |
uvelsurfobs |
Observed surface velocity, x-component (m yr⁻¹) |
vvelsurfobs |
Observed surface velocity, y-component (m yr⁻¹) |
Additional control variables (e.g. tau_ref, arrhenius) may be declared in variables; they are initialised from the relevant iceflow physics defaults.
observational data
It is important that observational data e.g. uvelsurfobs is defined as NaN where data is missing, not a real value e.g. 0.
Output
At each write (iteration 0, then every output.freq accepted optimiser iterations), the module writes a NetCDF file optimize.nc containing:
- The 2D fields listed in
output.vars_to_save. The computed quantitiesvelbase_mag,velsurf_mag,velsurfobs_mag, andsliding_ratioare derived automatically when requested. - Cost-function diagnostics: total cost, data misfit, regularization cost, and the full per-iteration cost history up to that snapshot.
- The current
retrain_iter_numcounter.
Example usage
Minimal inversion for ice thickness
defaults:
- override /assimilations:
- field_inversion
- override /processes:
- iceflow
assimilations:
field_inversion:
optimization:
nbitmax: 1000
minimizer_patience: 20
output:
freq: 200
vars_to_save: [usurf, thk, icemask, velsurf_mag, velsurfobs_mag]
variables:
- { name: thk, transform: identity, lower_bound: 0.0, upper_bound: 1000.0, mask: icemask }
objective:
misfit:
- { name: velsurf, kind: gaussian, components: [uvelsurf, vvelsurf],
obs: [uvelsurfobs, vvelsurfobs], std: 1.0 }
regularization:
- { name: thk, penalty: squared_laplacian, lam: 100000.0 }
Inversion with emulator retraining
assimilations:
field_inversion:
optimization:
nbitmax: 1000
minimizer_patience: 20
retrain_iter: 5
retrain_lr: 1.0e-5
retrain_steps: 500
retrain_anchor_weight: 0.01
variables:
- { name: thk, transform: identity, lower_bound: 0.0, upper_bound: 1000.0, mask: icemask }
objective:
misfit:
- { name: velsurf, kind: gaussian, components: [uvelsurf, vvelsurf],
obs: [uvelsurfobs, vvelsurfobs], std: 1.0 }
regularization:
- { name: thk, penalty: squared_laplacian, lam: 100000.0 }
Parameters
The complete default configuration file can be found here: field_inversion.yaml.
Structure of the parameters:
field_inversion
├── use_existing_thk
├── optimization
│ └── ...
├── output
│ └── ...
├── variables
└── objective
└── ...
Description of the parameters:
| Name | Description | Default value | Units |
|---|---|---|---|
use_existing_thk
|
False | ||
variables
|
List of control-variable specifications (name, transform, optional bounds, and mask) defining which fields are inverted. | [{'name': 'thk', 'transform': 'identity', 'lower_bound': 0.0, 'upper_bound': 1000.0, 'mask': 'icemask'}] | — |
optimization
| Name | Description | Default value | Units |
|---|---|---|---|
optimization.minimizer_patience
|
Number of iterations without improvement before the inner data-assimilation minimizer stops early (0 = disabled). | 10 | — |
optimization.retrain_lr
|
Learning rate for the network retraining steps. | 2e-5 | — |
optimization.retrain_iter
|
Number of times the procedure alternates between data-assimilation minimization and network retraining. | 5 | — |
optimization.nbitmax
|
Maximum optimizer iterations. | 500 | — |
optimization.retrain_anchor_weight
|
Weight penalizing deviation of the retrained network from the original pretrained network (L2 distance between the two networks' weights). | 0.1 | — |
optimization.retrain_replay_data_weight
|
Weight for the data-replay loss (replaying past training examples during retraining). | 0.0 | — |
optimization.retrain_replay_phys_weight
|
Weight for the physics-based replay loss (deviation of network predictions from the physics constraints on past training examples). | 0.0 | — |
optimization.retrain_steps
|
Number of network retraining gradient steps performed per retrain iteration. | 500 | — |
optimization.retrain_log_freq
|
Frequency (in retraining steps) at which retraining progress is logged. | 100 | — |
optimization.replay_data_dir
|
Path to the TFRecord dataset used for replay during network retraining. | — | |
optimization.replay_batch_size
|
Batch size used when sampling the replay dataset for retraining. | 4 | — |
optimization.replay_val_batches
|
Number of validation batches drawn from the replay dataset. | 4 | — |
optimization.replay_shuffle_buffer
|
Shuffle-buffer size for the replay dataset pipeline. | 2048 | — |
optimization.replay_split_seed
|
Random seed for the train/validation split of the replay dataset. | 0 | — |
optimization.replay_data_loss_type
|
Loss function for the replay data term (mse or huber). | huber | — |
optimization.replay_data_loss_huber_delta
|
Delta parameter of the Huber loss used for the replay data term. | 50.0 | m y\( ^{-1} \) |
output
| Name | Description | Default value | Units |
|---|---|---|---|
output.freq
|
Callback and NetCDF write cadence in iterations (iteration 0 is always written). | 100 | — |
output.vars_to_save
|
2D fields saved at each write. | ['usurf', 'thk', 'icemask', 'velbase_mag', 'velsurf_mag', 'velsurfobs_mag'] | — |
objective
| Name | Description | Default value | Units |
|---|---|---|---|
objective.misfit
|
List of data-misfit terms; each matches modelled fields against observations with a given error model and standard deviation. | [{'name': 'velsurf', 'kind': 'gaussian', 'components': ['uvelsurf', 'vvelsurf'], 'obs': ['uvelsurfobs', 'vvelsurfobs'], 'std': 1.0}] | — |
objective.regularization
|
List of regularization (penalty) terms applied to the control variables, each with a penalty type and weight. | [{'name': 'thk', 'penalty': 'squared_laplacian', 'lam': 1000000.0}] | — |
Contributors: Sebastian Rosier.