Gradient-Based Optimization
Sia Ghelichkhan
- / -
By the end of this workshop, we want to understand:
Forward modelling as a functional:
Where:
The forward problem:
Given
Example: Given initial temperature

The inverse problem asks the opposite question:
Given observations
Example — 1: Given observed surface velocities, find the initial temperature
Example — 2: Given observed surface deformation, find the rheology, or ice history that best explains them.
Challenge: This is usually ill-posed and requires regularization!

Key insight: We solve inverse problems by formulating them as optimization problems!
Measure misfit between predictions and observations:
Add regularization to stabilize the problem:
Components:
The key idea:
But
Reduced functional:
Now we have an unconstrained optimization problem:

The reduced functional “hides” the state variables by solving the forward problem implicitly.
Gradient-free methods (e.g., grid search):
Cost:
For mantle convection: Millions of unknowns → impractical!

Gradient-based methods:
Cost: One gradient computation (adjoint solve) per iteration
Key advantage: Gradient computation cost is independent of the number of control parameters!
Naive approach (finite differences):
Problem: Need
For mantle convection: Millions of parameters → millions of forward solves!

Solution: The adjoint method computes the gradient with only ONE additional solve!
The gradient via chain rule:
The expensive part:
Adjoint trick: Introduce adjoint variables
The adjoint equation:
Cost: One forward solve + one adjoint solve
Firedrake + pyadjoint automates the adjoint derivation:
from firedrake import *
from firedrake.adjoint import *
# Define control
T_IC = Function(Q, name="InitialTemperature")
control = Control(T_IC)
# Solve forward problem
T = solve_forward_problem(T_IC)
# Define objective functional
J = assemble(0.5 * (T - T_obs)**2 * dx)
# Create reduced functional
J_hat = ReducedFunctional(J, control)
# Compute derivative automatically!
dJ_dm = J_hat.derivative()What happens under the hood:
During forward solve, pyadjoint records:
The tape stores:
For adjoint solve, pyadjoint:
Key advantage: Works at PDE level, not line-by-line differentiation → maximum efficiency!
from gadopt import *
from gadopt.inverse import *
# 1. Set up forward problem
T_IC = Function(Q1, name="Control")
# 2. Solve forward problem
T_final, u = solve_mantle_convection(T_IC)
# 3. Define objective
J = 0.5 * assemble((T_final - T_obs)**2 * dx)
J += 0.5 * beta_s * assemble(dot(grad(T_IC),
grad(T_IC)) * dx)
# 4. Create reduced functional
J_hat = ReducedFunctional(J, Control(T_IC))
# 5. Optimize using ROL
T_IC_opt = minimize(J_hat, method='ROL',
options={'General': {...}})Key steps:
Core Concepts:
Firedrake + G-ADOPT provide:
.derivative()The power: Write forward problem → Get optimization framework for free!