Running DART as a CESM component
Welcome to the DART_interface user documentation!
Introduction
This documentation will guide you through the process of running DART within the CESM CIME framework.
The goal of the DART_interface is to allow “out-of-the-box” DA setup and execution for CESM models, within the ./case.setup, case.build, and case.submit workflow. This means that you can set up and run a data assimilation case with DART using the standard CESM workflow, without needing to manually configure DART or write custom scripts.
We will cover how to get the code, create a case, set up the case, how to change CESM options and DART namelist options, and finally run the case with data assimilation!
Please note that the science of Data Assimilation is never “out-of-the-box” particularly for coupled Earth system models. You will need to have some understanding of how DART works and how to configure it for your specific use case. This documentation is meant to guide you through the technical process of running DART within CESM, but it is not a tutorial on how to do data assimilation or how to configure DART for your specific use case. For more information on Data Assimilation with DART, visit the docs.dart.ucar.edu.
Get the code
You will need to clone the CROCODILE CESM repository and check out the branch with DART support. The branch is called full_regional_cesm_da. After checking out the branch, you will need to run git-fleximod update to download all the CESM components including DART.
git clone -b full_regional_cesm_da https://github.com/CROCODILE-CESM/CESM.git CESM_DA
cd CESM_DA/
./bin/git-fleximod update
Create a case
Compset choice
To use data assimilation you will need to set up a DA enabled compset.
You can see which compsets are available with DART using query__config:
cime/scripts/query_config --compsets dart
This will output something similar to:
Active component: dart
--------------------------------------
Compset Alias: Compset Long Name
--------------------------------------
C_DA : 2000_DATM%NYF_SLND_MOM6_DROF%NYF_SGLC_SWAV_DART%1
CR_JRA_DA : 1850_DATM%JRA_SLND_SICE_MOM6%REGIONAL_SROF_SGLC_SWAV_DART%1
CR1850MARBL_JRA_DA : 1850_DATM%JRA_SLND_SICE_MOM6%REGIONAL%MARBL-BIO_SROF_SGLC_SWAV_DART%1
CR_JRA_GLOFAS_DA : 1850_DATM%JRA_SLND_SICE_MOM6%REGIONAL_DROF%GLOFAS_SGLC_SWAV_DART%1
CR1850MARBL_JRA_GLOFAS_DA : 1850_DATM%JRA_SLND_SICE_MOM6%REGIONAL%MARBL-BIO_DROF%GLOFAS_SGLC_SWAV_DART%1
G_DA : 2000_DATM%NYF_SLND_CICE_MOM6_DROF%NYF_SGLC_SWAV_DART%1
G_JRA_DA : 2000_DATM%JRA-1p5-2023_SLND_CICE_MOM6_DROF%JRA-1p5-2023_SGLC_SWAV_DART%1
GR_JRA_DA : 1850_DATM%JRA_SLND_CICE_MOM6%REGIONAL_SROF_SGLC_SWAV_DART%1
GR1850MARBL_JRA_DA : 1850_DATM%JRA_SLND_CICE_MOM6%REGIONAL%MARBL-BIO_SROF_SGLC_SWAV_DART%1
GR_JRA_GLOFAS_DA : 1850_DATM%JRA_SLND_CICE_MOM6%REGIONAL_DROF%GLOFAS_SGLC_SWAV_DART%1
GR1850MARBL_JRA_GLOFAS_DA : 1850_DATM%JRA_SLND_CICE_MOM6%REGIONAL%MARBL-BIO_DROF%GLOFAS_SGLC_SWAV_DART%1
If you are creating a regional CESM case with CrocoDash, follow the
CrocoDash instructions to create
your CESM case. DART requires multiple instances of the model, which are the number of ensemble members in DART. Specify the number of ensemble members X with --ninst X along with --multi-driver for multiple instances.
If you are not using CrocoDash, you can follow the example below shows to create a case with a DA compset and 3 ensemble members using CESM’s create_newcase.
./cime/scripts/create_newcase \
--run-unsupported \
--res T62_t232 \
--compset G_DA \
--case /glade/work/$USER/da-cases/da-mom6-test.0001 \
--ninst 3 --multi-driver --project P86850054
--run-unsupported: This flag allows you to create a case that is not officially supported by the CESM team. This is necessary for using DART as a component because it is not yet an officially supported configuration.
--res T62_t232: This is the resolution of the model. T62 is the atmospheric resolution and t232 is the ocean resolution.
--compset G_DA: This specifies the component set to use. G_DA is a G compset with data assimilation using DART.
--case /glade/work/$USER/da-cases/da-mom6-test.0001 is the path where the case will be created. You can choose a different path if you prefer.
--ninst 3: This specifies the number of instances (ensemble members) to run.
--multi-driver: This flag indicates that the case will use multiple driver instances, which is necessary for
running DART with multiple ensemble members.
--project P86850054: This is the project code for the case, which is used for accounting purposes on the computing system. You should replace this with your own project code.
Set up the case
cd into the case directory and run ./case.setup to set up the case. This will create the necessary directories and files for the case.
cd /glade/work/$USER/da-cases/da-mom6-test.0001
./case.setup
Case Options
There are a few CESM options that need to be set for the case. These are set with xmlchange commands. The options are:
./xmlchange CALENDAR=GREGORIAN
./xmlchange DATA_ASSIMILATION_OCN=TRUE
./xmlchange RUN_STARTDATE=2013-04-01
./preview_namelists --component esp
./case.setup --reset
CALENDAR=GREGORIAN: required when DART is active. The build stops with an error if the
calendar is anything else.
DATA_ASSIMILATION_OCN=TRUE: turns on assimilation for the ocean. Set
DATA_ASSIMILATION_ATM, DATA_ASSIMILATION_LND or DATA_ASSIMILATION_ICE to TRUE for
the other components. All components with DA enabled must have the same NINST (the same
ensemble size). At least one of these must be TRUE when you build the case (./case.build builds
filter and friends only for the components you have turned on). Once the case is built,
you can set all four to FALSE to run cycles with DA turned off entirely – see
Running without DA (spin-up).
DATA_ASSIMILATION_CYCLES: a CIME variable giving the number of times CIME stops
the model and calls the DA script within one ./case.submit. The default is
DATA_ASSIMILATION_CYCLES=1.
To run multiple DA cycles in one job submittion, set DATA_ASSIMILATION_CYCLES>1
./xmlchange DATA_ASSIMILATION_CYCLES=3
Each cycle assimilate.py is passed a
cycle number that starts at 0 and counts up within that job submission only – a
resubmitted job starts back at cycle == 0 for its first stop, no matter how many cycles
earlier submissions already completed. Anything keyed off cycle == 0 (see
Inflation on the first cycle and
Ensemble perturbation on cycle 0 below) has to account
for that.
RUN_STARTDATE: the start date of the run.
Note
Start date is important, as this is used to match observations to model output. Change the start date to match the observations you want to assimilate. The date format is YYYY-MM-DD.
./preview_namelists --component esp runs the DART buildnml on its own. Do this
before ./case.build, because the first time buildnml runs with at least one active
DA component, it sets NTASKS_ESP (the number of tasks DART runs filter on) to the
largest NTASKS of the active DA components. NTASKS_ESP lives in env_mach_pes.xml,
which CESM locks when you run ./case.setup. Running preview_namelists now, followed by
./case.setup --reset, gets NTASKS_ESP set and env_mach_pes.xml re-locked while there
is nothing built yet.
Warning
If you skip preview_namelists, NTASKS_ESP is instead changed part way through
./case.build, when CESM generates the namelists. The changed env_mach_pes.xml no
longer matches the locked copy, so ./case.submit refuses to run and tells you to run
./case.setup --reset. ./case.setup --reset sets BUILD_COMPLETE=FALSE, so you then
have to build the whole case a second time.
Note
NTASKS_ESP is only ever set by buildnml while it is still at CESM’s default value of
1 – i.e. the very first time. Once it has been set, buildnml leaves it alone, even
though it reruns on every ./case.submit. This means you only need the
preview_namelists / case.setup --reset dance above once, right after your first
case.setup. Toggling DATA_ASSIMILATION_* flags later, to change which components are
assimilated or to turn DA off entirely, does not change NTASKS_ESP and does not
require repeating this step.
./case.setup --reset picks up the xml changes above and re-locks env_mach_pes.xml.
You can query the options with ./xmlquery to make sure they are set correctly:
./xmlquery CALENDAR
Or see all options with
./xmlquery --listall
Or see matching options with
./xmlquery --partial DATA_ASSIM
DART is the ESP component, so its task count and root directory are the ESP variables:
./xmlquery --partial ESP
Build the case
./case.build
Cleaning and rebuilding DART
If you change DART source code, or otherwise need to force DART be rebuilt, clean the ESP component before building again:
./case.build --clean esp
./case.build
--clean esp only removes DART’s build (the ESP component); it does not clean the build of the other CESM components so ./case.build only has to rebuild DART.
List of observation sequence files
The list of observation sequence files DART will use is in stored in
Buildconf/dart.input_data_list
This file is created by preview_namelists based on your DART_OBS_ROOT setting.
See Observations for the directory layout and naming
convention DART expects, and how to point DART_OBS_ROOT at your own
observation archive.
DART input.nml
DART’s runtime options are in a Fortran namelist file called input.nml. You do not edit
this file directly. Instead, DART’s buildnml writes one input.nml per active DA
component, starting from a template of DART defaults for that component’s model and
applying your changes from user_nl_dart.
The dart input.nml created by preview_namelists is in
Buildconf/dartconf/input.nml.ocn
Since in this example we have set DATA_ASSIMILATION_OCN=TRUE, we are only assimilating ocean observations and therefore the input.nml file is input.nml.ocn. If you were assimilating atmosphere observations, the input.nml file would be input.nml.atm and so on for land and sea-ice.
During the run, assimilate.py copies input.nml.{comp} to $RUNDIR/input.nml before
running filter_{comp} for that component, once per component per assimilation cycle.
Note
MOM6 also reads a file called input.nml, in the same run directory. assimilate.py
backs the MOM6 file up as mom_input.nml.bak before staging DART’s, and restores it when
filter has finished, so the input.nml you find in $RUNDIR after a run is MOM6’s, not
DART’s. Look in Buildconf/dartconf/ for DART’s.
Changing namelist options with user_nl_dart
./case.setup puts a user_nl_dart in your case directory. Add the namelist groups and
variables you want to change, using normal Fortran namelist syntax:
&filter_nml
inf_flavor = 5, 0
inf_initial = 1.0, 1.0
inf_sd_initial = 0.6, 0.0
/
&assim_tools_nml
cutoff = 0.1
/
user_nl_dart applies to all active DA components.
Component specific namelist options
The components assimilate different observations, of different quantities, at different
scales, so when running DA on mulitple components you will need to set some options
per component rather than shared.
To set an option for one component only, create a user_nl_dart_{comp} file in your case
directory:
File |
Component |
DART model |
|---|---|---|
|
ocean |
MOM6 |
|
atmosphere |
CAM ( |
|
land |
CLM |
|
sea-ice |
CICE |
Note
./case.setup creates user_nl_dart for you, but not the user_nl_dart_{comp} files.
Create the ones you need yourself, in $CASEROOT.
Values are merged variable by variable, in this order, last one wins:
the DART default for that component, from
param_templates/json/input_nml_{model}.jsonuser_nl_dartuser_nl_dart_{comp}
So a user_nl_dart_{comp} file only needs to contain the variables that differ from
user_nl_dart; you can use both files together. For example, to use a 0.1 radian
localization everywhere except the ocean, where you want 0.05:
# user_nl_dart
&assim_tools_nml
cutoff = 0.1
/
# user_nl_dart_ocn
&assim_tools_nml
cutoff = 0.05
/
input.nml.ocn is then written with cutoff = 0.05, and every other active component
gets cutoff = 0.1.
Namelist options you should not set
Some options are set from the case xml variables and are overwritten by buildnml after
your user_nl_dart changes are applied. Setting these in user_nl_dart has no effect:
Namelist variable |
Set from |
|---|---|
|
fixed as |
|
fixed as |
|
|
|
fixed at |
Warning
Do not set filter_nml:ens_size in user_nl_dart. It is set from NINST of the active
DA components, but unlike the variables in the table above, a value in user_nl_dart
overrides it. That does not change the number of ensemble members CESM runs, it only
makes input.nml disagree with the case, and filter will fail. Change the ensemble
size with --ninst at create_newcase instead.
Only variables that already exist in the component’s template can be set. A misspelled
variable, or one that belongs to a DART program that is not part of this component’s
build, is skipped with a warning rather than being written to input.nml:
Variable inf_sd_intial in filter_nml not found in template
Namelist filtr_nml not found in template
Checking your changes
Regenerate the namelists and look at the result:
./preview_namelists --component esp
This rewrites Buildconf/dartconf/input.nml.{comp} and prints any of the warnings above,
so it is worth doing whenever you edit user_nl_dart. Namelist changes do not need a
rebuild: CESM regenerates the namelists during ./case.submit.
Inflation on the first cycle
When using inf_initial_from_restart=.true. and inf_sd_initial_from_restart=.true., on the first cycle of a new case,
there are no existing inflation files for filter to read.
assimilate.py turns the from_restart flags off for the first cycle and filter starts from
the nameslist values (e.g. 0.6, 0.6) for inf_initial and inf_sd_initial.
Once the first inflation file exists, inf_initial_from_restart and
inf_sd_initial_from_restart are set back to .true.
Warning
inf_sd_initial must be greater than zero for inflation to adapt. With
inf_sd_initial = 0.0, which is the template default, the zero is written into the
inflation restart and read back on every subsequent cycle, so inflation stays fixed at
inf_initial for the whole run, not just the first cycle. 0.6 is a common starting
value.
Ensemble perturbation on cycle 0
A cold-start multi-instance case starts every instance from an identical
restart, so the ensemble has no spread until something perturbs it. Set
filter_nml:perturb_from_single_instance = .true. in user_nl_dart for this case: filter
then reads only the first instance’s restart and perturbs it into the full ensemble,
instead of reading all NINST per-instance restarts.
assimilate.py restricts perturb_from_single_instance = .true. to cycle == 0:
it is forced back to .false. on every cycle > 0, regardless of the setting in user_nl_dart. If you
leave the namelist value .false. (the template default), nothing is forced on at any
cycle. perturbation_amplitude controls the size of the cycle-0 perturbation and is
ignored once perturb_from_single_instance is .false..
Warning
If you resubmit a job mid-experiment with perturb_from_single_instance still .true.,
CIME’s cycle counter restarts at 0 for the new job submission (see
DATA_ASSIMILATION_CYCLES above), so cycle 0 perturbs again and discards
the ensemble spread already built up by assimilation. Set it back to .false. in
user_nl_dart before resubmitting an in-progress multi-submittion experiment.
Running without DA (spin-up)
You may want to run some cycles without assimilating anything – for example a spin-up
period before starting DA, or to pause assimilation partway through an experiment. To do
this, set all four DATA_ASSIMILATION_* flags to FALSE:
./xmlchange DATA_ASSIMILATION_OCN=FALSE
With no active DA components, buildnml skips namelist generation for DART and
assimilate.py logs that it is skipping DA and returns, so ./case.submit runs the model
forward without calling filter for any component. Turn one or more flags back on to
resume assimilation on a later cycle; no rebuild or case.setup --reset is needed either
way, since NTASKS_ESP is fixed at build time (see the note above).
Note
You must still build the case with at least one DATA_ASSIMILATION_* flag TRUE –
./case.build requires at least one active component so that the corresponding
filter_{comp} executable exists. Turning all flags off only skips assimilation at run
time; it does not change what was built.
Run the case
./case.submit