-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Differentiability tests for time stepping #656
base: main
Are you sure you want to change the base?
Changes from 61 commits
94f202d
986086f
90c95b1
b657098
757dfaa
f4d3ca8
b354516
8935181
8026ec5
ed522fa
21e1500
2db1583
ad43ef7
be8d08c
684aef6
c96bd74
9701b28
055f496
e0d44ac
8ff6b3a
5e414aa
6a421c0
28372ae
c6561c5
5158af7
92e42fe
52aad60
4d5b0ef
ea96381
60e28fa
141dc28
6a9f6cc
922f0c1
cd3e837
5889981
bf1f842
130de65
26141d9
50dd2ec
a3f1638
4dc8944
ece4449
65823f0
1375887
67e0dac
b27609e
525ef09
21af75d
36f9160
a968050
0f472fd
af14a22
d05eb7d
2635431
3375674
796b331
6a7767e
06de07d
16dc412
cff60e1
7bda523
42ce046
7a49401
f82883a
926d699
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# Differentiability and Adjoint Model | ||
|
||
SpeedyWeather.jl is written with differentiability in mind. This means that our model is differentiable by automatic differentiation (AD). If you are interested in machine learning (ML), this means that you can integrate our model directly into your ML models without the need to train your ANNs seperately first. For atmospheric modellers this means that you get an adjoint model for free which is always generated automatically, so that we don't need to maintain it seperatly. So, you can calibrate SpeedyWeather.jl in a fully automatic, data-driven way. | ||
|
||
!!! Work in progress | ||
The differentiability of SpeedyWeather.jl is still work in progress and some parts of this documentation might be not be always updated to the latest state. We will extend this documentation over time. Don't hesitate to contact us via GitHub issues or mail when you have questions or want to colloborate. | ||
|
||
For the differentiability of our model we rely on [Enzyme.jl](https://github.com/EnzymeAD/Enzyme.jl). If you've used Enzyme before, just go ahead and try to differentiate the model! It should work. We have checked the correctness of the gradients extensively against a finite differences differentiation with [FiniteDifferences.jl](https://github.com/JuliaDiff/FiniteDifferences.jl/). In the following we present a simple example how we can take the gradient of a single timestep of the primitive equation model with respect to one of the model parameter. | ||
|
||
!!! Enzyme with Julia 1.11 | ||
Currently there are still some issues with Enzyme in Julia 1.11, we recommend to use Julia 1.10 for the following | ||
|
||
First we initialize the model as usual: | ||
|
||
```@example autodiff | ||
using SpeedyWeather, Enzyme | ||
|
||
spectral_grid = SpectralGrid(trunc=23, nlayers=3) | ||
model = PrimitiveWetModel(; spectral_grid) | ||
simulation = initialize!(model) | ||
initialize!(simulation) | ||
run!(simulation, period=Day(10)) # spin-up the model a bit | ||
``` | ||
|
||
Then, we get all variables we need from our `simulation` | ||
|
||
```@example autodiff | ||
(; prognostic_variables, diagnostic_variables, model) = simulation | ||
(; Δt, Δt_millisec) = model.time_stepping | ||
dt = 2Δt | ||
|
||
progn = prognostic_variables | ||
diagn = diagnostic_variables | ||
``` | ||
|
||
Next, we will prepare to use Enzyme. Enzyme saves the gradient information in a shadow of the original input. For the inputs this shadow is initialized zero, whereas for the output the shadow is used as the seed of the AD. In other words, as we are doing reverse-mode AD, the shadow of the output is the value that is backpropageted by the reverse-mode AD. Ok, let's initialize everything: | ||
|
||
```@example autodiff | ||
dprogn = one(progn) # shadow for the progn values | ||
ddiagn = make_zero(diagn) # shadow for the diagn values | ||
dmodel = make_zero(model) # here, we'll accumulate all parameter derivatives | ||
``` | ||
|
||
Then, we can already do the differentiation with Enzyme | ||
|
||
```@example autodiff | ||
autodiff(Reverse, SpeedyWeather.timestep!, Const, Duplicated(progn, dprogn), Duplicated(diagn, ddiagn), Const(dt), Duplicated(model, dmodel)) | ||
``` | ||
|
||
The derivitaves are accumulated in the `dmodel` shadow. So, if we e.g. want to know the derivative with respect to the gravity constant, we just have to inspect: | ||
|
||
```@example autodiff | ||
dmodel.planet.gravity | ||
``` | ||
|
||
Doing a full sensitivity analysis through a long integration is computationally much more demanding, and is something that we are currently working on. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,18 @@ function Base.show(io::IO, C::Clock) | |
print_fields(io, C, keys) | ||
end | ||
|
||
# copy! | ||
function Base.copy!(clock::Clock, clock_old::Clock) | ||
clock.time = clock_old.time | ||
clock.start = clock_old.start | ||
clock.period = clock_old.period | ||
clock.timestep_counter = clock_old.timestep_counter | ||
clock.n_timesteps = clock_old.n_timesteps | ||
clock.Δt = clock_old.Δt | ||
|
||
return nothing | ||
end | ||
Comment on lines
+42
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks good, but why is that needed? I'm suspecting to restart a simulation? Cause in that case we'd need to check whether all of them are supposed to just be copied over or whether some of them should be set differently afterwards There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As you see in the code of the test, we really need to copy the structs quite often. I also sometimes use out-of-place forms of the functions, so my intend was to really have them identical if I copy them. Intuitively that's also what I would expect from a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No I agree that's what I expect a julia> struct S
a
end
julia> s = S(1)
S(1)
julia> deepcopy(s)
S(1) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, |
||
|
||
""" | ||
$(TYPEDSIGNATURES) | ||
Initialize the clock with the time step `Δt` in the `time_stepping`.""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding Enzyme to the docs and actually running the example code, makes the whole CI process for the docs much longer. So long actually, the process timed out. We may also just hardcode the Enzyme example in the docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy with that for now! We can later check how to automate tests if that's currently unfeasible