Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Data Assimilation with DART–CESM

Run a real forecast–assimilate–update cycle: a 3-member regional MOM6 ensemble in CESM, updated every 24 hours (once each day) by the observations you made in Tutorial 1.

A typical DART-CESM experiment consists of the following steps:

  1. Generate the model domain.

  2. Create a multi-instance (ensemble) CESM case.

  3. Configure the case for assimilation.

  4. Stage your observations

  5. Build and submit the case.

  6. Examine what the assimilation did.

This is Part 3 of the DART tutorial series:
1. Working with Real Observations · 2. Creating Synthetic Observations · 3. Data Assimilation with DART–CESM

For more detail on the DART interface to CESM, such as what compsets are available with DA, see the DART_interface documentation. For much more detail on DART take a look at the DART documentation or learn about research with DART at dart.ucar.edu.

SECTION 1: The model domain

Step 1.1: Set Up Your Experiment Parameters

Step 2.2: Generate the tutorial domain

Domain generation is taught in the CrocoDash tutorial. Here run the three cells that build a Hawaii domain, because creating a case needs the live grid, topography, and vertical-grid objects.

SECTION 3: Create a multi-instance CESM case

Step 3.1: Set the case details

Make sure cesmroot points at the CESM_DA directory.

Step 3.2: Create the case

One new concept compared to the CrocoDash tutorial is running multi-instance CESM. An ensemble filter needs an ensemble, that is a group of model forcasts. CESM runs ninst copies (“instances”) of the model. In an assimilation experiment, each ensemble member (instance) starts from slightly different initial conditions. The spread of the ensemble members is what gives us information on model uncertainty.

For more detail on ensemble data assimilation, see the DART introduction to ensemble data assimilation.

Three members is workshop-sized, small enough to build and run in a tutorial session. Real ocean DA experiments use 30–80 members, and spin up the oceans from different initial conditions to get ensemble spread. We will perturb the ensemble members in this tutorial to generate ensemble spread.

Step 3.3: Prepare forcing data

Exactly as in the CrocoDash tutorial: make sure the forcing covers the date range you are interested in.

SECTION 4: Configure the Case for Assimilation

We’ll now swap to the terminal to use CESM’s xmlchange command to query and set our experiment options.

We’ll build and run our case on Derecho, so from Casper connect to Derecho with:

ssh casper
ssh derecho

Step 4.1: Turn on data assimilation

XML settings turn a regional ocean case into a cycling DA experiment. We run xmlchange in the case directory, so in the terminal cd to your case directory:

cd /glade/work/$USER/crocodile2026/workspace/hawaii/

We’re going to set several XML variables to:

  • Turn on data assimilation for the ocean.

  • Stop the model each day to assimilate observations - we’re going to run 3 one-day cycles, centered on midnight.

  • Turn off short term archiving so our output files stay in the run directory - for this tutorial we only have 3 days of output.

  • Tell CESM-DART where to find our observations.

./xmlchange DATA_ASSIMILATION_OCN=TRUE
./xmlchange STOP_OPTION=ndays,STOP_N=1
./xmlchange DATA_ASSIMILATION_CYCLES=3
./xmlchange DOUT_S=FALSE
./xmlchange DART_OBS_ROOT=/glade/campaign/cgd/oce/projects/CROCODILE/workshops/2026/dart/obs/real

Notes on xmlquery

You can see what CESM variables are set to with ./xmlquery.

./xmlquery CALENDAR

For data assimilation we use the GREGORIAN calendar, rather than the default CESM NO_LEAP calendar.

You can query multiple variables at once:

./xmlquery STOP_OPTION STOP_N DATA_ASSIMILATION_CYCLES DOUT_S JOB_QUEUE NTASKS ROOTPE_OCN DART_OBS_ROOT DATA_ASSIMILATION

Or search for matches to a string:

./xmlquery --partial DATA_ASSIM

Notes on observation files

Step 4.2 Set the DART namelist Options

The namelist options for dart are set in user_nl_dart. Note user_nl_dart applies to all active DA components. If you are running DA with multiple components you may want to set component specific namelist options. Since we are just running ocean assimilation, we’l use user_nl_dart.

The are several key namelists for ocean DA: &model_nml, &obs_kind_nml, &assim_tools_nml and &filter_nml.

Open user_nl_dart in your case directory, using “open from path”

/work/crocodile2026/workspace/hawaii/user_nl_dart

Copy and past the following namelist options into user_nl_dart and remember to click save!

&model_nml
use_pseudo_depth = .true. 
layer_name = 'Layer'
model_state_variables        = 'Salt ', 'QTY_SALINITY             ', 'NA', 'NA', 'UPDATE',
                               'Temp ', 'QTY_POTENTIAL_TEMPERATURE', 'NA', 'NA', 'UPDATE',
                               'u    ', 'QTY_U_CURRENT_COMPONENT  ', 'NA', 'NA', 'UPDATE',
                               'v    ', 'QTY_V_CURRENT_COMPONENT  ', 'NA', 'NA', 'UPDATE',
/

&obs_kind_nml
assimilate_these_obs_types = 'ARGO_SALINITY', 'ARGO_TEMPERATURE'
/

&assim_tools_nml
cutoff = 0.008
/

&filter_nml
perturb_from_single_instance = .true.
perturbation_method = 'model'
stages_to_write = 'output', 'preassim'
num_output_state_members = 3
output_members               = .true.
/

During the CESM build step, the namelist file DART will read is written to to Buildconf/dartconf/input.nml.ocn. We can use ./preview_namelists in the case directory to check input.nml.ocn is created correctly:

./preview_namelists --comp esp

./preview_namelists will also create list of observation files DART use in Buildconf/dart.input_data_list.

Check Buildconf/dart.input_data_list contains the files you expect.

What the Namelist Options Do

&model_nml:

Sets model specific options - which MOM6 variables are in the state vector and what vertical coordinate we want to use.

  • We’ve set Salt, Temperature, U and V current in our model state - only theses state-vector variables are updated by filter.

  • We’ve set pseudo depth as the vertical.

&obs_kind_nml:

Sets which observation types are used by filter:

  • assimilate_these_obs_types - these observations are allowed to impact the model state.

  • evaluate_these_obs_types - these observations do not impact the model state, but their forward operator is computed and recorded.

For this tutorial we’ll assimilate ARGO_SALINITY and ARGO_TEMPERATURE. After assimilation the DART QC indicates whether an observation was evaluate only or used in the assimilation: DART outgoing quality control.

&filter_nml and &assim_tools_nml are where the main assimilation options are set.

For much more detail on DART options take a look at the filter namelist documentation. The two main options for ensemble DA and inflation and localization.

SettingMeaning
inf_flavor, inf_initialinflation, grows ensemble spread to counter the overconfidence of small ensembles
cutofflocalization half-width in radians, limits how far one observation reaches.
0.2 rad ≈ 11.5° ≈ 1,275 km at the equator

We’re not using inflation in this tutorial, but it is a great topic for the project time. The default cutoff is 0.2 radians which would be larger than our entire 5°×5° Hawaii domain, so we’ve set cutoff much smaller at 0.008 radians.

Generating Spread

If three ensemble members have the same initial conditions and the same forcing data, they will all output the same results. This is no good for data assimilation, which requires some ensemble spread.

Generating ensemble spread is an interesting scientific topic. Typically small random perturbations are added to each ensemble member, then the models are integrated until system can stabilize, reach physical balance, and provide a realistic spread of model states for data assimilation. Alternative approaches in include running a model for a long time and creating an ensemble by using the state from the same day each year. For example, running for 40 years, and taking the 40 January 1st as though they occurred on the same year.

For this tutorial we are going to generate ensemble spread by perturbing the temperature of each ensemble member. Note a realistic data assimilation experiment would take time to spin up the model.

Step 4.3 Using the cpudev Queue on Derecho

Since we have a small domain, we’ll reduce the number of tasks per instance.

./xmlchange JOB_QUEUE=develop
./xmlchange NTASKS=16
./xmlchange ROOTPE_OCN=0

CESM will tell us that for our changes to take effect we have to run:

./case.setup --reset

Step 4.4 Atmospheric forcing

The JRA atmosphere can take a long time to initialize so we are going to restrict its search to a single year by running this script in the case directory:

/glade/campaign/cgd/oce/projects/CROCODILE/workshops/2026/dart/set_atm_forcings.sh

Step 4.5: Build the case

qcmd -A UCGD0009 -- ./case.build

The build takes a while. Let’s take a look at how the assimilation will while we wait. The job will execute 3 cycles data assimilation since DATA_ASSIMILATION_CYCLES=3:


               one cycle
            ┌──────────────────────────────────────────────────────────┐
            ▼                                                          │
 1. FORECAST      all 3 MOM6 instances advance 24 hours (1 day)        │
 2. FILTER        CESM's ESP layer calls DART filter:                  │
                    reads obs_seq.<window>.out + all 3 model states    │
                    computes the ensemble update                       │
                    writes diagnostics (obs_seq.final, *assim_mean.nc) │
 3. UPDATE        updated restart files replace the forecast restarts  │
 4. ADVANCE       CESM resubmits the next 24-hour segment ─────────────┘

SECTION 5: Submit the Data Assimilation Experiment

Submitting the case will run the DATA_ASSIMILATION_CYCLES in one jobs submission.

./case.submit

Each cycle, filter writes diagnostics into the run directory (file names include the case name, dart, component, and the cycle timestamp):

FileContents
obs_seq.finalEvery observation with its prior (and optionally posterior) forward operator results, and DART QC value
output_mean.ncUpdated ensemble mean
output_sd.ncUpdated ensemble spread

The files in your run directory will be called something like hawaii.dart.ocn_obs_seq_final.2023-06-18-00000 which is {case}.dart.{component}_obs_seq_final.{timestamp}.

Since we are also outputting state members there will be files for the individual ensemble members named *member_0001, *member_0002, *member_0003.

SECTION 6: Examine the assimilation

State space diagnostics evaluate model variables and error covariances directly in the system’s physical or model coordinate domain, whereas observation space diagnostics map model estimates to the measurement (observation) domain.

Step 6.1: Observation-space diagnostics

obs_seq.final contains information about which observations were used in assimilation, and why any observations were not used. It also contains the prior and optionally the posterior forward operator values for each observation and their mean and spread.

pyDARTdiags can be used to calculate and plot observation-space diagnostics from obs_seq.final.

We’ll look at used vs. rejected observations, and the prior and posterior statistics for an observation type.

Let’s take a look at a profile plot, and what this is telling us about the assimilation. We’ll take a look at the Argo temperature observations, binned at the depths below.

Let’s also print a summary of the the number of observations that were available (possible) and the number of observations that were used by filter, with obs_seq.possible_vs_used()

If you have observations that were not used, take a deeper dive into the DART QC value.

Interpreting the assimilation diagnostics

When you look at your plot, consider the following questions:

  1. Did the assimilation change the bias? Compare the prior and posterior bias curves. Where do they differ, and at what depths? A change in bias indicates that the assimilation has moved the ensemble mean relative to the observations. Ideally, assimilation should reduce the magnitude of the bias, although this may not occur everywhere.

  2. Did the assimilation change the RMSE? Compare the prior and posterior RMSE curves. Where does the RMSE increase or decrease? A decrease indicates better agreement between the ensemble and the observations at those depths. Also look at the overall RMSE reported in the statistics box.

  3. Where does the assimilation have the largest effect? Look for depths where the prior and posterior curves are most separated. These are regions where the observations are having the greatest impact on the analysis.

  4. Does the impact occur where observations are available? Compare the locations of the observations with changes in the prior and posterior curves. Do the largest changes occur near observed depths? If not, the assimilation may be spreading observational information to other depths through the ensemble covariance and localization.

  5. What happened to the ensemble spread? Compare the prior and posterior total spread. Does the spread decrease after assimilation? A decrease is expected because the observations provide additional information and constrain the ensemble.

  6. Did the spread decrease everywhere, or only at particular depths? Look at the shape of the prior and posterior spread curves. A localized reduction in spread can indicate where the observations are constraining the ensemble most strongly.

  7. Did the reduction in spread correspond to an improvement in RMSE or bias? This is an important question. A smaller posterior spread means the ensemble is more tightly clustered, but it does not necessarily mean that the analysis is more accurate. Compare the changes in spread with the changes in bias and RMSE.

  8. Has the ensemble become too tightly constrained? If the posterior spread is much smaller than the prior spread but the RMSE has not improved, this may indicate that the ensemble has become underdispersive. In other words, the ensemble is more confident without necessarily being more accurate.

  9. What do the overall statistics tell you? Compare the prior and posterior values in the Grand statistics box. Do the changes in the overall bias, RMSE, and total spread agree with what you see in the depth profiles?

Step 6.2: State-space diagnostics

Look at the model state before and after assimilation.

filter writes the ensemble mean state for each stage in stages_to_write. Comparing the mean before assimilation (preassim_mean.nc) to the mean after assimilation (output_mean.nc) gives the increment:

increment = posterior mean - prior mean

Increments show where the observations moved the model state. plot_state_space(TIMESTAMP) plots the surface Temp field for the cycle given by TIMESTAMP (the same cycle you looked at in Step 6.1), pre- and post-assimilation, the increment between them, and the locations of the observations that were actually assimilated during that cycle.

Recap

Your takeaway: a DA-enabled case you can rerun and reconfigure, plus obs_seq.final and increment maps from your own 3-cycle experiment.

Where to go from here?

  • Run longer: set END = 2023-09-30 in Tutorial 1, regenerate the observations, and increase DATA_ASSIMILATION_CYCLES.

  • Run an OSSE: swap in synthetic observations from Tutorial 2.

  • More members: raise ninst and examine how spread, inflation, and RMSE respond.

  • More observations: add GLIDER_* or BOTTLE_* types in Tutorial 1. Start them in evaluate mode.

  • Spin up an ensemble for a case you are interested in. How will you create spread?

  • Dive into the DART documentation, review the CESM DART_interface documentation, explore pyDARTdiags for observation space diagnostics, and try the Crocodile gallery’s mom6-tools page to look the mean ensemble member.