Summary
There is currently no way to get an Omega output stream to write a record for the initial state. IOStream::writeAll() is invoked from exactly one place — inside the time-stepping loop in ocnRun, after doStep() — so the earliest record any write stream can produce is at t = dt, never at t = 0.
Details
In src/ocn/OceanRun.cpp, the only call is at line 68, within the while loop:
while (Err == 0 && !(EndAlarm->isRinging())) {
++IStep;
...
DefTimeStepper->doStep(DefOceanState, SimTime);
// write restart file/output, anything needed post-timestep
IOStream::writeAll(OmegaClock);
...
}
There is no writeAll() (or equivalent per-stream write()) call before the loop begins, so neither of the two mechanisms that might otherwise produce an initial record can fire:
-
Interval alarms. For standard time units, IOStream::create builds Alarm(AlarmName, AlarmInt, ClockStart) (src/infra/IOStream.cpp:494). The first ring is one interval after the clock start, so an interval-based History stream never emits a t = 0 frame regardless of Freq.
-
FreqUnits: OnStartup. The flag is parsed and stored (src/infra/IOStream.cpp:499), and writeStream honors it via the StartupShutdown condition (src/infra/IOStream.cpp:2480-2482), but because the first writeAll() happens after step 1 has already completed, an OnStartup write stream produces a record stamped t = dt rather than t = 0. OnStartup is therefore usable for read streams (HorzMeshIn, InitialState, InitialVertCoord) but not meaningfully for write streams.
Impact
MPAS-Ocean output streams do write a record at the reference time, so an MPAS-Ocean output.nc contains both the initial and final states while the corresponding Omega output.nc contains only the final state. This asymmetry breaks any post-processing that compares the first and last frames of a single output file.
Concretely, in Polaris this makes conservation checks vacuous for Omega. polaris/ocean/model/ocean_model_step.py implements property checks such as 'mass conservation' by evaluating a quantity at Time=0 and Time=-1 of the model output and comparing. For a convergence task where the output interval equals the run duration, the Omega file holds a single frame, so the check compares that frame against itself and trivially reports zero drift. The same check is meaningful for MPAS-Ocean. We hit this while porting the geostrophic (Williamson case 2) task to Omega in E3SM-Project/polaris#622 and chose to leave the conservation check disabled rather than include one that passes for the wrong reason.
Suggested fix
Call IOStream::writeAll(OmegaClock) once before entering the time loop in ocnRun (or equivalently at the end of ocnInit, after all fields have been initialized and attached). That would let OnStartup write streams emit a genuine initial-state record, and would let a History stream optionally capture t = 0 in addition to its interval-based frames.
The multi-frame append logic in writeStream already looks like it would handle the extra frame correctly: it reads the elapsed time of each existing frame and either overwrites a matching one or appends the next, so a t = 0 frame written first and a t = Freq frame written later should land as frames 0 and 1 of the same file.
Open questions
- Are all fields that a
History stream might reference guaranteed to be computed and attached at the point where a pre-loop writeAll() would run, or would some diagnostics be written as fill values? The answer is almost certainly "no", and we would need the ability to compute diagnostic fields on startup to support this capability.
- For restart runs, should a startup write be suppressed to avoid duplicating the record at the restart time, or is the existing overwrite-on-matching-elapsed-time path in
writeStream sufficient? My guess is that we would want to avoid writing on restart.
Summary
There is currently no way to get an Omega output stream to write a record for the initial state.
IOStream::writeAll()is invoked from exactly one place — inside the time-stepping loop inocnRun, afterdoStep()— so the earliest record any write stream can produce is att = dt, never att = 0.Details
In
src/ocn/OceanRun.cpp, the only call is at line 68, within thewhileloop:There is no
writeAll()(or equivalent per-streamwrite()) call before the loop begins, so neither of the two mechanisms that might otherwise produce an initial record can fire:Interval alarms. For standard time units,
IOStream::createbuildsAlarm(AlarmName, AlarmInt, ClockStart)(src/infra/IOStream.cpp:494). The first ring is one interval after the clock start, so an interval-basedHistorystream never emits at = 0frame regardless ofFreq.FreqUnits: OnStartup. The flag is parsed and stored (src/infra/IOStream.cpp:499), andwriteStreamhonors it via theStartupShutdowncondition (src/infra/IOStream.cpp:2480-2482), but because the firstwriteAll()happens after step 1 has already completed, anOnStartupwrite stream produces a record stampedt = dtrather thant = 0.OnStartupis therefore usable for read streams (HorzMeshIn,InitialState,InitialVertCoord) but not meaningfully for write streams.Impact
MPAS-Ocean output streams do write a record at the reference time, so an MPAS-Ocean
output.nccontains both the initial and final states while the corresponding Omegaoutput.nccontains only the final state. This asymmetry breaks any post-processing that compares the first and last frames of a single output file.Concretely, in Polaris this makes conservation checks vacuous for Omega.
polaris/ocean/model/ocean_model_step.pyimplements property checks such as'mass conservation'by evaluating a quantity atTime=0andTime=-1of the model output and comparing. For a convergence task where the output interval equals the run duration, the Omega file holds a single frame, so the check compares that frame against itself and trivially reports zero drift. The same check is meaningful for MPAS-Ocean. We hit this while porting the geostrophic (Williamson case 2) task to Omega in E3SM-Project/polaris#622 and chose to leave the conservation check disabled rather than include one that passes for the wrong reason.Suggested fix
Call
IOStream::writeAll(OmegaClock)once before entering the time loop inocnRun(or equivalently at the end ofocnInit, after all fields have been initialized and attached). That would letOnStartupwrite streams emit a genuine initial-state record, and would let aHistorystream optionally capturet = 0in addition to its interval-based frames.The multi-frame append logic in
writeStreamalready looks like it would handle the extra frame correctly: it reads the elapsed time of each existing frame and either overwrites a matching one or appends the next, so at = 0frame written first and at = Freqframe written later should land as frames 0 and 1 of the same file.Open questions
Historystream might reference guaranteed to be computed and attached at the point where a pre-loopwriteAll()would run, or would some diagnostics be written as fill values? The answer is almost certainly "no", and we would need the ability to compute diagnostic fields on startup to support this capability.writeStreamsufficient? My guess is that we would want to avoid writing on restart.