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.

Creating Synthetic Observations with perfect_model_obs

Sample a model state as if it were the real ocean - the first step in an Observing System Simulation Experiment (OSSE)!

A typical workflow for generating synthetic observations consists of the following steps:

  1. Design an observing network: we’ll use the locations from Tutorial 1, plus some random ones.

  2. Run a “truth” or nature run of your model: MOM6 for this tutorial

  3. Use dartobsgen with perfect_model_obs create the synthetic observations

  4. Examine the synthetic observations you have created.

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

SECTION 1: Why synthetic observations?

In an OSSE you treat a model run as the “truth”. You sample your model run where your instruments would sample the real ocean, and add realistic errors such as instrument noise and representativeness error. Because you know the truth exactly, you can use OSSEs to estimate how new instruments and observing networks will impact your data assimilation.

OSSEs answer questions like:

  • Is my DA plumbing working at all? (If you can’t recover a known truth, real obs won’t help.)

  • Where should new instruments go to constrain the circulation feature I care about?

  • How does observation accuracy trade off against observation count?

perfect_model_obs is DART’s tool for the sampling step: it reads a model state, applies the same forward operators the assimilation would use, and perturbs each value with noise drawn from the observation error variance you assign.

SECTION 2: Set Up Your Experiment Parameters

dartobsgen’s PerfectModelSource drives perfect_model_obs for you, one assimilation window at a time. It needs three things:

  1. the compiled perfect_model_obs executable,

  2. an input.nml containing a &perfect_model_obs_nml block

  3. a MOM6 state to sample, the “truth”.

For this tutorial, we’ll use a MOM6 run of the Hawaii domain as model truth, and the perfect_model_obs from /glade/campaign/cgd/oce/projects/CROCODILE/workshops/2026/dart/pmo.

For each window, PerfectModelSource writes a template obs_seq.in holding your network (locations, types, times, error variances), sets the DART namelist for each window, runs the executable in an isolated subdirectory under <PMO_RUN_DIR>/windows/, and collects the resulting obs_seq.out.

SECTION 3: Design the observation network

Step 3.1: Harvest the Tutorial 1 locations

Often in an OSSE we want to sample the truth where instruments actually were. Let’s use the locations of the real observations from Tutorial 1. We read the first Tutorial 1 window with pyDARTdiags and turn its unique locations into ObsNetworkEntry objects, one per location per observation type.

One thing changes compared to Tutorial 1: the error variance is now yours to choose. Real converters carry the instrument error with the data; in an OSSE the “instrument” is imaginary, so you decide how good it is. We use (0.2 °C)² for temperature and (0.1 PSU)² for salinity, typical Argo-like values.

Step 3.2: Add random locations

Now the part you can’t do with real data: invent instruments. We add 20 random profile locations inside the domain, each sampling the standard depths. The seeded random generator means everyone in the workshop gets the same “random” network. You can change the seed to generate a different network.

Step 3.3: Map the network

Plot the two networks, the locations harvested from Tutorial 1 and the random additions, together.

SECTION 4: Generate The Synthetic Observations

Step 4.1: Configure dartobsgen For Our New Observation Network

ObsGenConfig is almost the same as Tutorial 1. We want the same time period, same bounding box, same observation types, same assimilation frequency, but we want our output written to a different directory: SYNTHETIC_OBS_DIR. The source for “observations” is now a MOM6 run rather than CrocoLake, so we use PerfectModelSource rather than CrocoLake. Because the “truth” changes from day to day, we also give it a MOM6StateProvider built over the MOM6 history output, so each window samples the model state valid at that day rather.

Step 4.2: Look inside

perfect_model_obs has sampled the MOM6 truth state at each location with the DART forward operator and added noise drawn from your obs_err_var. Before moving on, let’s verify that the following is true in your observation sequence files:

  • ARGO_TEMPERATURE should read as plausible °C for the Hawaii domain (roughly 20-28 °C near the surface), varying smoothly with depth.

  • ARGO_SALINITY is in kg/kg, not PSU — DART’s MOM6 forward operator returns salinity in kg/kg regardless of what units you’re used to thinking in. A realistic value is ~0.034-0.035, not ~35. If you see values near zero, or negative, salinity isn’t real — it means noise is swamping the signal, almost always because obs_err_var was set in PSU² instead of kg/kg².

  • obs_err_var for temperature and salinity will look very different in magnitude (e.g. 0.04 vs 1e-8) — that’s expected, since they’re different quantities in different units. What matters is that sqrt(obs_err_var) is small relative to the actual observed value, not that the two numbers look similar to each other.

Recap

Your takeaway artifact: A directory obs/synthetic/ocn_obs_seq containing synthetic observations under your WORKSPACE.

Where to go from here?