Skip to content

Commit 7a12e46

Browse files
committed
Rewrite calibration pipeline
This commit rewrites the calibration pipeline to use ClimaCalibrate.ObservationRecipe and ClimaAnalysis for the data preprocessing and for making the observations. Additionally, this calibration pipeline is now closer to the calibration pipeline used for ClimaCoupler.
1 parent 2879a7e commit 7a12e46

17 files changed

+1067
-953
lines changed

.buildkite/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ GeoMakie = "db073c08-6b98-4ee5-b6a4-5efafb3259c6"
2525
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
2626
Insolation = "e98cc03f-d57e-4e3c-b70c-8d51efe9e0d8"
2727
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
28+
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
2829
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
2930
LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3"
3031
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

docs/src/calibration.md

Lines changed: 173 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,26 @@ CAL.calibrate(
4242
)
4343
```
4444

45-
where the utki object defines your EKP configurations, for example, the default is:
45+
where the `utki` object defines your EKP configurations, for example, the default is:
4646

4747
```julia
48-
observationseries,
49-
EKP.TransformUnscented(prior, impose_prior = true);
48+
EKP.EnsembleKalmanProcess(
49+
obs_series,
50+
EKP.TransformUnscented(prior, impose_prior = true),
5051
verbose = true,
51-
rng,
52+
rng = rng,
5253
scheduler = EKP.DataMisfitController(terminate_at = 100),
54+
)
5355
```
5456

55-
where `observationseries` is the "truth" you want to calibrate your model on. It can take many forms.
57+
where `obs_series` is the "truth" you want to calibrate your model on. It can take many forms.
5658
For example, you may want to calibrate your land model latent heat flux (lhf), the
5759
observations could be monthly global average of lhf, or monthly average at 100 random locations on land, or the annual amplitude and phase...
58-
You will create `observationsseries` from some data (for example ERA5), as a vector.
60+
You will create `obs_series` from some data (for example ERA5), as a vector.
5961

60-
note that `observationseries` object contains the covariance matrix of the noise, which informs the uncertainties in space and time of your targeted "truth". It can be set, for example, to the inter-annual variance of a variable, or to the average of the variable times a % (e.g., 5%), or to a flat noise (for example, 5 W m-2 for latent heat). This will inform the EKP algorithm that if the model is within the target +- noise at specific space and time, the goal is reached.
62+
Note that `obs_series` object contains the covariance matrix of the noise, which informs the uncertainties in space and time of your targeted "truth". It can be set, for example, to the inter-annual variance of a variable, or to the average of the variable times a % (e.g., 5%), or to a flat noise (for example, 5 W m-2 for latent heat). This will inform the EKP algorithm that if the model is within the target +- noise at specific space and time, the goal is reached.
6163

62-
`TransformUnscented.Unscented` is a method in EKP, that requires `2 x number of parameters + 1` ensemble members (`ensembe_size`, the number of parameter set drawn for your prior distribution tested at each iteration). For more information, read the [EKP documentation for that method](https://clima.github.io/EnsembleKalmanProcesses.jl/dev/unscented_kalman_inversion/).
64+
`TransformUnscented.Unscented` is a method in EKP, that requires `2 x number of parameters + 1` ensemble members (`ensemble_size`, the number of parameter set drawn for your prior distribution tested at each iteration). For more information, read the [EKP documentation for that method](https://clima.github.io/EnsembleKalmanProcesses.jl/dev/unscented_kalman_inversion/).
6365

6466
`verbose = true` is a setting that writes information about your calibration run to a log file.
6567

@@ -134,7 +136,7 @@ like this:
134136
│ │ │ └── output_active -> output_0000
135137
│ │ └── parameters.toml
136138
```
137-
each iteration contains folders for each member, inside which you can find the parameters value inside `parameters.toml`, and model outputs inside `global_diagnostics`.
139+
Each iteration contains folders for each member, inside which you can find the parameters value inside `parameters.toml`, and model outputs inside `global_diagnostics`.
138140

139141
Two additional functions need to be defined in order to run `CAL.calibrate`. `CAL.forward_model(iteration, member)` and `observation_map(iteration)`.
140142
The `CAL.forward_model(iteration, member)` needs to generate your model output for a specific iteration and member. The `observation_map(iteration)`
@@ -168,7 +170,7 @@ export CLIMACOMMS_DEVICE="CUDA"
168170
export CLIMACOMMS_CONTEXT="SINGLETON"
169171
julia --project=.buildkite -e 'using Pkg; Pkg.instantiate(;verbose=true)'
170172

171-
julia --project=.buildkite/ experiments/calibration/global_land/calibrate_land.jl
173+
julia --project=.buildkite/ experiments/calibration/run_calibration.jl
172174
```
173175

174176
and below is a example job .sh script (for Slurm, e.g., central or clima):
@@ -190,7 +192,7 @@ export CLIMACOMMS_CONTEXT="SINGLETON"
190192
# Build and run the Julia code
191193
module load climacommon
192194
julia --project=.buildkite -e 'using Pkg; Pkg.instantiate(;verbose=true)'
193-
julia --project=.buildkite/ experiments/calibration/calibrate_land.jl
195+
julia --project=.buildkite/ experiments/calibration/run_calibration.jl
194196
```
195197

196198
where `calibrate_land.jl` is a script that generates all the arguments needed and eventually calls `CAL.calibrate`.
@@ -199,33 +201,163 @@ You would start the job with a command such as `qsub name_of_job_script` for PBS
199201

200202
Note that with the default EKP configuration, UTKI, the number of ensemble is set by the number of parameters, as explained in the documentation above. The number of workers (if you use the worker backend) is automatically set to that numbers, so that all members are run in parallel for each iteration.
201203

202-
## Configure your calibration job
203-
204-
To run a calibration, you can modify the following objects in `calibrate_land.jl`:
205-
- include `forward_model.jl` or `forward_model_bucket.jl`, depending on which model you want to calibrate
206-
- `variable_list`: choose the variables you want to calibrate. For example, `["swu"]` or `["lhf", "shf"]`
207-
- `n_iterations`: how many iterations you want to run
208-
- `spinup_period`: the time length of your spinup
209-
- `start_date`: the start date of the calibration
210-
- `nelements`: to adjust the model resolution (n_horizontal elements, n_vertical elements)
211-
- `caldir`: you can change the name and path of your calibration output directory
212-
- `training_locations`: the default is all locations on land, but you can change this to a subset of land coordinates
213-
214-
You should also modify the files:
215-
- `priors.jl`, to give your parameters and their distribution
216-
- `forward_mode.jl` or `forward_model_bucket.jl`, to ensure it reads the parameters from `priors.jl` and uses them
217-
218-
other things to consider:
219-
- `observationseries_era5.jl`: currently the target data is era5. This could be changed to another dataset, or a perfect model target
220-
- `observationseries_era5.jl`: the noise is currently set to 5^2 (flat noise, in W m^-2), this should be changed to desired noise
221-
- you could change the EnsembleKalmanProcesses settings, set in `EKP.EnsembleKalmanProcesses()` in `calibrate_land.jl`
222-
- you can adjust the ClimaCalibrate backend, see the [documentation](https://clima.github.io/ClimaCalibrate.jl/dev/backends/)
223-
- Because the variable names are currently different in era5 file, you may have to add variables to map in observationseries_era5.jl (for example, "lhf" => "mslhf")
224-
225-
Also, note that currently:
226-
- the temporal resolution of observation_maps are seasonal (3 months average)
227-
- the length of calibration is 1 year (data used after spinup)
228-
- the entire globe is used
229-
These could also be changed in the code, but would currently requires significant changes.
230-
231-
Finally, note that the HPC job script and command will slightly differ between slurm (for example central or clima) and pbs (for example derecho).
204+
## Configure your land calibration
205+
206+
For configuring the land calibration, you can configure the optimization method
207+
used by EnsembleKalmanProcesses.jl, the observations and how they are
208+
preprocessed, and the simulation itself. For most settings, you can modify
209+
the `CalibrateConfig` struct. See the example below.
210+
211+
```julia
212+
CalibrateConfig(;
213+
short_names = ["swu"],
214+
minibatch_size = 2,
215+
n_iterations = 10,
216+
sample_date_ranges = [("2007-12-1", "2008-9-1"),
217+
("2008-12-1", "2009-9-1"),
218+
("2009-12-1", "2010-9-1"),
219+
("2010-12-1", "2011-9-1")],
220+
extend = Dates.Month(3),
221+
spinup = Dates.Month(3),
222+
nelements = (101, 15),
223+
output_dir = "experiments/calibration/land_model",
224+
rng_seed = 42,
225+
)
226+
```
227+
228+
With the configuration above, a calibration is being done using the `swu`
229+
observation. The calibration will run for 10 iterations, and each iteration will
230+
use a minibatch size of 2. The start and end dates of the simulation is
231+
automatically determined by `sample_date_ranges`, `spinup`, and `extend`. The
232+
amount to spin up the simulation for is three months and the amount to run the
233+
simulation for past the dates in `sample_date_ranges` is also three months.
234+
Since the minibatch size is 2, the first iteration of the calibration will run
235+
from 1 September 2007 to 1 December 2009 and the second iteration of the
236+
calibration is 1 September 2009 to 1 December 2011. Afterward, the iterations
237+
will repeat, so the third iteration will be the same as the first iteration, the
238+
fourth iteration will be the same as the second iteration, and so on.
239+
240+
The period chosen for `extend` is ensure that all the data is gathered. If you
241+
are calibrating against seasonal averages, then `extend` should be 3 months and
242+
if you are calibrating against monthly averages, then `extend` should be 1
243+
month. For more information, see the sections [Data pipelines](@ref) and
244+
[Simulation settings](@ref).
245+
246+
The number of horizontal and vertical elements to use for the simulation is
247+
determined by `nelements`. The calibration is saved at `output_dir` and the
248+
random number generator is seeded by `rng_seed`.
249+
250+
### EKP settings
251+
252+
In `CalibrateConfig`, you can modify the number of iterations via `n_iterations`
253+
and the size of the minibatch by `minibatch_size`. For reproducibility, you can
254+
pass in an integer for `rng_seed` which is used internally by
255+
EnsembleKalmanProcesses.jl to seed the random number generator.
256+
257+
For the land calibration, the default optimization method is
258+
`EnsembleKalmanProcesses.TransformUnscented` and the default scheduler is
259+
`EKP.DataMisfitController`. To change this optimization method or change the
260+
scheduler, you need to go to the `experiments/calibration/run_calibration.jl`
261+
file and modify it there. For more information about the different optimization
262+
methods, see
263+
[EnsembleKalmanProcesses.jl](https://clima.github.io/EnsembleKalmanProcesses.jl/dev/)
264+
for more information.
265+
266+
### Observation settings
267+
268+
In `CalibrateConfig`, you can modify which observations that are used via
269+
`short_names`. As of now, the currently supported observations for calibration
270+
are seasonal averages of `lhf`, `shf`, `lwu`, and `swu`. See the
271+
[Data pipelines](@ref) on how to add more variables.
272+
273+
Observations are generated with `generate_observations.jl`. To generate
274+
observations, you can run
275+
276+
```julia
277+
julia --project=.buildkite experiments/calibration/generate_observations.jl`
278+
```
279+
280+
!!! note "When do I need to generate observations?"
281+
Whenever `nelements`, `sample_date_ranges`, `short_names`, or the
282+
preprocessing of any of the variables change, then you must regenerate the
283+
observations!
284+
285+
Each observation contains time series data of the variables from ERA5 and a
286+
covariance matrix. You can adjust `sample_date_ranges` if you want to calibrate
287+
over a particular year or over multiple years for example.
288+
289+
!!! note "What can be passed for `sample_date_ranges`?"
290+
The sample date ranges should be times from the time series data of the
291+
observations. For example, when using seasonal averages, the times passed
292+
must be the first day of December, March, June, or September. The seasons
293+
are December to February (DJF), March to May (MAM), June to August (JJA),
294+
and September to November (SON). The convention for time is use the first
295+
time of the time reduction.
296+
297+
To change the covariance matrix, you need to go to `generate_observations.jl`
298+
and modify the covariance matrix that is passed in. See
299+
[ClimaCalibrate.jl documentation](https://clima.github.io/ClimaCalibrate.jl/dev/api/#Observation-Recipe-Interface)
300+
for a list of different covariance matrices that can be used.
301+
302+
#### Data pipelines
303+
304+
!!! note "Data preprocessing"
305+
The data preprocessing uses ClimaAnalysis.jl. See the
306+
[ClimaAnalysis.jl documentation](https://clima.github.io/ClimaAnalysis.jl/dev/api/)
307+
for functions you can use for preprocessing.
308+
309+
To add additional variables or change how a variable is preprocessed, you must
310+
add the variable to the data sources and specify how the variable should be
311+
preprocessed. The name of the variable should match the name in the diagnostics.
312+
As of now, each variable from the ERA5 data is loaded by the function
313+
`get_era5_obs_var_dict` in `ext/land_sim_vis/leaderboard/data_sources.jl`. See
314+
the documentation of `get_obs_var_dict` for how to add a new variable. In
315+
addition, you should also add the same variable to `get_sim_var_dict` in the
316+
same file as before.
317+
318+
Further preprocessing is done in the function `preprocess_single_era5_var` in
319+
`generate_observations.jl`. After adding the variable, you should specify any
320+
additional preprocessing. For example, if you want seasonal averages, you should
321+
use `ClimaAnalysis.average_season_across_time`. Furthermore, you should check
322+
the units of the variable, ensure that the grid and mask are the same between
323+
the simulation and observational data, and ensure the conventions for times are
324+
the same. Finally, the same preprocessing should be applied between both
325+
observational and simulation data. Preprocessing the observational data is done
326+
in `preprocess_single_era5_var` in
327+
`experiments/calibration/generate_observations.jl` and preprocessing the
328+
simulation data is done in `process_member_data` in
329+
`experiments/calibration/observation_map.jl`.
330+
331+
!!! note "Time conventions"
332+
Different data sources have different conventions for time. For example,
333+
data sources use January 1, January 15, and Feburary 1 for the monthly
334+
average of January. For the diagnostics saved from a CliMA simulation, the
335+
current convention is to save the monthly average on the Feburary 1. You
336+
must ensure that the time conventions are the same. For calibration, we
337+
choose the start of the time reduction, so for this example, the time
338+
associated with the monthly average of January is January 1. For seasonal
339+
averages, the dates would be December 1, March 1, June 1, and September 1.
340+
341+
### Simulation settings
342+
343+
In `CalibrateConfig`, you can modify the resolution of the model via
344+
`nelements`. To change the directory of where the calibration starts, you can
345+
change `output_dir`, whose default is `experiments/calibration/land_model`.
346+
347+
For adjusting the start and end dates of the simulation, you can change `spinup`
348+
and `extend`.
349+
350+
!!! note "How are the start and end dates of the simulation chosen?"
351+
The start and end dates of a simulation is automatically inferred from the
352+
`sample_date_ranges`. For example, suppose that `sample_date_ranges =
353+
[("2007-12-1", "2008-9-1"), ("2008-12-1", "2008-9-1)]` and the minibatch
354+
size is 2. The start and end dates are `2007-12-1` to `2008-9-1`. This
355+
should cover the time ranges of the observational data. However, observe
356+
that the simulation output may not be realistic at the beginning of the
357+
simulation while the land model is equilibrating and the end date is not
358+
correct, because of the time convention we chosen earlier. To solve the
359+
first problem, you can specify how the long the model should spin up for
360+
with `spinup` in the `CalibrateConfig`. For the second problem, you can make
361+
the simulation extend past `2008-9-1` with `extend`. For calibrating with
362+
monthly averages, `extend` should be `Dates.Month(1)` and for calibrating
363+
with seasonal averages, `extend` should be `Dates.Month(3)`.

experiments/calibration/PBS_calibration.pbs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
#PBS -N derecho_calibration
33
#PBS -o output.txt
44
#PBS -e error.txt
5-
#PBS -l walltime=12:00:00
6-
#PBS -l select=1:ncpus=4:ngpus=1
5+
#PBS -l walltime=6:00:00
6+
#PBS -l select=1:ncpus=1:ngpus=1
77

88
## Account number for CliMA
99
#PBS -A UCIT0011
1010
#PBS -q main
1111

12+
## Note: Using this script requires changes to addprocs in the
13+
## experiments/calibration/run_calibration.jl
14+
1215
export PBS_ACCOUNT="UCIT0011"
1316
export MODULEPATH="/glade/campaign/univ/ucit0011/ClimaModules-Derecho:$MODULEPATH"
1417
module load climacommon
@@ -17,4 +20,4 @@ export CLIMACOMMS_DEVICE="CUDA"
1720
export CLIMACOMMS_CONTEXT="SINGLETON"
1821
julia --project=.buildkite -e 'using Pkg; Pkg.instantiate(;verbose=true)'
1922

20-
julia --project=.buildkite/ experiments/calibration/calibrate_land.jl
23+
julia --project=.buildkite/ experiments/calibration/run_calibration.jl

0 commit comments

Comments
 (0)