Classical inversion tutorial
This tutorial provides a simple example on how to perform a classical gridded inversion in ODINN.jl. For this, we generate a synthetic dataset using a forward simulation, and then we use this dataset to perform the classical inversion. The goal of this classical inversion is to retrieve the matrix of A values associated to the Glen coefficient/rigidity in Glen's Law that was used to generate the results of a forward simulation.
Step 1: Parameter and glacier initialization
using ODINNWe fetch the paths with the files for the available glaciers on disk
rgi_paths = get_rgi_paths()Dict{String, String} with 83 entries:
"RGI60-11.00897" => "per_glacier/RGI60-11/RGI60-11.00/RGI60-11.00897"
"RGI60-08.00213" => "per_glacier/RGI60-08/RGI60-08.00/RGI60-08.00213"
"RGI60-08.00147" => "per_glacier/RGI60-08/RGI60-08.00/RGI60-08.00147"
"RGI60-11.02787" => "per_glacier/RGI60-11/RGI60-11.02/RGI60-11.02787"
"RGI60-11.03646" => "per_glacier/RGI60-11/RGI60-11.03/RGI60-11.03646"
"RGI60-11.01270" => "per_glacier/RGI60-11/RGI60-11.01/RGI60-11.01270"
"RGI60-11.02773" => "per_glacier/RGI60-11/RGI60-11.02/RGI60-11.02773"
"RGI60-11.03232" => "per_glacier/RGI60-11/RGI60-11.03/RGI60-11.03232"
"RGI60-01.22174" => "per_glacier/RGI60-01/RGI60-01.22/RGI60-01.22174"
"RGI60-07.00274" => "per_glacier/RGI60-07/RGI60-07.00/RGI60-07.00274"
"RGI60-11.00597" => "per_glacier/RGI60-11/RGI60-11.00/RGI60-11.00597"
"RGI60-11.02801" => "per_glacier/RGI60-11/RGI60-11.02/RGI60-11.02801"
"RGI60-11.01876" => "per_glacier/RGI60-11/RGI60-11.01/RGI60-11.01876"
"RGI60-03.04207" => "per_glacier/RGI60-03/RGI60-03.04/RGI60-03.04207"
"RGI60-11.02739" => "per_glacier/RGI60-11/RGI60-11.02/RGI60-11.02739"
"RGI60-04.04351" => "per_glacier/RGI60-04/RGI60-04.04/RGI60-04.04351"
"RGI60-07.00065" => "per_glacier/RGI60-07/RGI60-07.00/RGI60-07.00065"
"RGI60-11.02346" => "per_glacier/RGI60-11/RGI60-11.02/RGI60-11.02346"
"RGI60-01.00570" => "per_glacier/RGI60-01/RGI60-01.00/RGI60-01.00570"
⋮ => ⋮Define which glacier RGI IDs we want to work with
rgi_ids = ["RGI60-11.03638"]1-element Vector{String}:
"RGI60-11.03638"Define the time step for the simulation output, in this case, a month.
δt = 1/120.08333333333333333We now define the parameters used for the simulation
params = Parameters(
simulation = SimulationParameters(
use_MB = false,
tspan = (2010.0, 2015.0),
test_mode = false,
multiprocessing = false, # We are processing only one glacier
rgi_paths = rgi_paths,
gridScalingFactor = 4), # Downscale the glacier grid to speed-up this example for the GitHub servers
hyper = Hyperparameters(
batch_size = length(rgi_ids), # Set batch size equals size of the dataset
epochs = [2, 2], # [35,30]
optimizer = [
ODINN.Adam(0.02),
ODINN.LBFGS(
linesearch = ODINN.LineSearches.BackTracking(iterations = 5)
)
]),
physical = PhysicalParameters(
minA = 8e-21,
maxA = 8e-17),
UDE = UDEparameters(
optim_autoAD = ODINN.NoAD(),
empirical_loss_function = LossH() # Loss function based on ice thickness
),
solver = Huginn.SolverParameters(step = δt) # Save simulation every one month
)Parameters
physical ρ = 900.0 · A ∈ [8.0e-21, 8.0e-17] · C ∈ [8.5e-20, 8.0e-17]
simulation tspan = (2010.0, 2015.0) · ✓ iceflow ✗ MB ✓ velocities
solver RDPK3Sp35 · reltol = 1.0e-12 · maxiters = 100000
hyper epochs = [2, 2] · batch_size = 1 · optimizer = [Adam, LBFGS]
UDE target = :A · "AD+AD" · ContinuousAdjoint · loss = LossH
Step 2: Generate synthetic ground truth data with a forward simulation
We define a synthetic law to generate the synthetic dataset. For this, we use some tabular data from Cuffey and Paterson (2010) [2] in a law relating ice temperature with the coefficient A. This law is already available in ODINN.jl, which we specify to be in a gridded format which means that it varies spatially (i.e. non-scalar).
A_law = CuffeyPaterson(scalar = false)
model = Model(
iceflow = SIA2Dmodel(params; A = A_law),
mass_balance = TImodel1(params; DDF = 6.0 / 1000.0, acc_factor = 1.2 / 1000.0)
)**** Model ****
------- Detailed info about the Shallow Ice Approximation (SIA) -------
The SIA is a 2D low order model that approximates the glacier dynamics.
In the ODINN ecosystem this can be parameterized through different variables, and with the current status of the model:
- H̄ is the ice thickness on the dual grid
- ∇S is the ice surface slope, given by ∇S=∇(B+H) with B the bedrock elevation
- D is the diffusivity
- U is the flux
- A is the rheology of the ice in Glen's flow law
- n is the exponent in Glen's flow law
- ρ and g are resp. the ice density and the gravity constant
- C is the sliding coefficient in the Weertman sliding law
- p is the stress exponent in the Weertman sliding law
- q is the effective-pressure exponent in the Weertman sliding law
> These details will be shown only once. To show them in future prints set model.show_status .= 1;
SIA2D iceflow equation = ∇(D ∇S) with D = U H̄
and U = C (ρg)^(pq) H̄^(pq+1) ∇S^(p-1) + Γ H̄^(n+2) ∇S^(n-1)
Γ = 2A (ρg)^n /(n+2)
A: (:T,) -> Matrix{Float64} (↧@start )
C: ConstantLaw -> Array{Float64, 0}
n: ConstantLaw -> Array{Float64, 0}
p: ConstantLaw -> Array{Float64, 0}
q: ConstantLaw -> Array{Float64, 0}
where
T => averaged_gridded_long_term_temperature
Temperature index mass balance model TImodel1
DDF = 0.006
acc_factor = 0.0012
No learnable components
***************We initialize the glaciers with all the necessary data:
glaciers = initialize_glaciers(rgi_ids, params)1-element Vector{Glacier2D} distributed over regions 11 (x1)
RGI60-11.03638
Time snapshots where to store data for the inversion:
tstops = collect(2010:δt:2015)61-element Vector{Float64}:
2010.0
2010.0833333333333
2010.1666666666667
2010.25
2010.3333333333333
2010.4166666666667
2010.5
2010.5833333333333
2010.6666666666667
2010.75
⋮
2014.3333333333333
2014.4166666666667
2014.5
2014.5833333333333
2014.6666666666667
2014.75
2014.8333333333333
2014.9166666666667
2015.0We generate the synthetic dataset using the forward simulation. This will generate a dataset with the ice thickness and surface velocities for each glacier at each time step. The dataset will be used to make the inversion hereafter.
prediction = generate_ground_truth_prediction(glaciers, params, model, tstops)
glaciers = prediction.glaciers1-element Vector{AbstractGlacier} distributed over regions 11 (x1)
RGI60-11.03638
Now we compute the spatially varying A to have a ground truth for the comparison at the end of this tutorial.
A_ground_truth = zeros(size(prediction.glaciers[1].H₀))
A_ground_truth[1:(end - 1), 1:(end - 1)] .= eval_law(
prediction.model.iceflow.A, prediction, 1,
(; T = get_input(iAvgGriddedTemp(), prediction, 1, tstops[1])), nothing)
A_ground_truth[prediction.glaciers[1].H₀ .== 0] .= NaN;Step 3: Model specification to perform a classical inversion
After this forward simulation, we restart the iceflow model to be ready for the inversions
trainable_model = GriddedInv(params, glaciers, :A)
A_law = LawA(params; scalar = false)
model = Model(
iceflow = SIA2Dmodel(params; A = A_law),
mass_balance = TImodel1(params; DDF = 6.0 / 1000.0, acc_factor = 1.2 / 1000.0),
regressors = (; A = trainable_model)
)**** Model ****
------- Detailed info about the Shallow Ice Approximation (SIA) -------
The SIA is a 2D low order model that approximates the glacier dynamics.
In the ODINN ecosystem this can be parameterized through different variables, and with the current status of the model:
- H̄ is the ice thickness on the dual grid
- ∇S is the ice surface slope, given by ∇S=∇(B+H) with B the bedrock elevation
- D is the diffusivity
- U is the flux
- A is the rheology of the ice in Glen's flow law
- n is the exponent in Glen's flow law
- ρ and g are resp. the ice density and the gravity constant
- C is the sliding coefficient in the Weertman sliding law
- p is the stress exponent in the Weertman sliding law
- q is the effective-pressure exponent in the Weertman sliding law
> These details will be shown only once. To show them in future prints set model.show_status .= 1;
SIA2D iceflow equation = ∇(D ∇S) with D = U H̄
and U = C (ρg)^(pq) H̄^(pq+1) ∇S^(p-1) + Γ H̄^(n+2) ∇S^(n-1)
Γ = 2A (ρg)^n /(n+2)
A: () -> Matrix{Float64} (↧@start custom VJP ✅ precomputed)
C: ConstantLaw -> Array{Float64, 0}
n: ConstantLaw -> Array{Float64, 0}
p: ConstantLaw -> Array{Float64, 0}
q: ConstantLaw -> Array{Float64, 0}
Temperature index mass balance model TImodel1
DDF = 0.006
acc_factor = 0.0012
Learnable components
A: --- Param to invert ---
Matrix per glacier
θ: ComponentVector of length 1088
***************Step 4: Perform the inversion by optimizing the model
We specify the type of simulation we want to perform
inversion = Inversion(model, glaciers, params)Inversion
glaciers 1 glacier
model iceflow = SIA2Dmodel · mass_balance = TImodel1 · learnable =
A: --- Param to invert ---
Matrix per glacier
θ: ComponentVector of length 1088
parameters
physical ρ = 900.0 · A ∈ [8.0e-21, 8.0e-17] · C ∈ [8.5e-20, 8.0e-17]
simulation tspan = (2010.0, 2015.0) · ✓ iceflow ✗ MB ✓ velocities
solver RDPK3Sp35 · reltol = 1.0e-12 · maxiters = 100000
hyper epochs = [2, 2] · batch_size = 1 · optimizer = [Adam, LBFGS]
UDE target = :A · "AD+AD" · ContinuousAdjoint · loss = LossH
cache (nothing)
results ✗ not yet run
And finally, we just run the simulation
run!(inversion)[ Info: Optimizing with ADAM
[ Info: Optimizing with custom ContinuousAdjoint{Float64, Int64, DiscreteVJP{ADTypes.AutoMooncake{Nothing}}, EnzymeVJP} method
Iteration: [ 1 / 4] Loss:2.29993e+00
Iteration: [ 2 / 4] Loss:2.10338e+00 Improvement: -8.55 %
Iteration: [ 3 / 4] Loss:2.10338e+00 Improvement: 0.00 %
[ Info: Optimizing with LBFGS
[ Info: Optimizing with custom ContinuousAdjoint{Float64, Int64, DiscreteVJP{ADTypes.AutoMooncake{Nothing}}, EnzymeVJP} method
Iteration: [ 4 / 4] Loss:2.10338e+00 Improvement: 0.00 %
Iteration: [ 5 / 4] Loss:1.07828e+00 Improvement: -48.74 %
Iteration: [ 6 / 4] Loss:4.56952e-01 Improvement: -57.62 %Now that the model has been optimized, we retrieve the inverted parameters. These parameters do not correspond directly to the values of A. What it defines instead is a parameterization of A to ensure positiveness through a tanh function.
θ = inversion.results.stats.θComponentVector{Float64}(A = (1 = [-0.00010001000133356149 -0.00010001000133356149 … -0.00010001000133356149 -0.00010001000133356149; -0.00010001000133356149 -0.00010001000133356149 … -0.00010001000133356149 -0.00010001000133356149; … ; -0.00010001000133356149 -0.00010001000133356149 … -0.00010001000133356149 -0.00010001000133356149; -0.00010001000133356149 -0.00010001000133356149 … -0.00010001000133356149 -0.00010001000133356149]))We map the parameters to the values of A by evaluating the law.
A = zeros(size(inversion.glaciers[1].H₀))
inn1(A) .= eval_law(inversion.model.iceflow.A, inversion, 1, (;), θ)
A[inversion.glaciers[1].H₀ .== 0] .= NaN;Step 5: Compare the inverted parameter to the synthetic ground truth
Finally we visualize the inverted A.
plot_gridded_data(A, inversion.results.simulation[1]; colormap = :YlGnBu, logPlot = true)We can compare it to the ground truth A values:
plot_gridded_data(A_ground_truth, inversion.results.simulation[1]; colormap = :YlGnBu, logPlot = true)Unsurprisingly the inverted A is noisy in comparison to the ground truth. This is because the inversion requires regularization. The fact that we are using only the ice thickness might also not help. Adding a second target observation in the loss function, i.e. the ice surface velocities, would help. For more information on how to define regularizations, see the Optimization section.
This page was generated using Literate.jl.