Fix fill values in all Omega fields#428
Conversation
7fc03ea to
8fc6203
Compare
8fc3284 to
6625253
Compare
|
It looks to me like it should be possible to automatically deduce the right fill value from the array type when calling |
6625253 to
8cc604f
Compare
|
@mwarusz, great idea! I'll see if I can incorporate that quickly into this PR. |
|
@xylar Here are some suggestions on how to do it cleanly in C++ that you might find useful. You could define the fill values in // Primary template intentionally undefined.
template <typename T>
constexpr T FillValue;
template <>
constexpr I4 FillValue<I4> = -2147483647; ///< NC_FILL_INT
template<>
constexpr I8 FillValue<I8> = -9223372036854775806LL; ///< NC_FILL_INT64
...Then if The main benefit of doing it this way is that you don't have to write a long chain of |
8cc604f to
147144f
Compare
|
@mwarusz, I like that. I'll implement it and run some tests, then I'll ask you to review once I've got it working. |
5fb3358 to
29d8f99
Compare
|
@mwarusz and @alicebarthel, I got optimistic about nearly being done and assigned you to review. But I still have some kinks to work out. I'll take this out of draft mode once it's working (hopefully tomorrow) and ping you again for a review. |
fba980b to
079b462
Compare
079b462 to
0804a22
Compare
TestingI tested this after rebasing onto I am using the polaris branch E3SM-Project/polaris#603 for testing to make sure that the higher-order horizontal advection fix still works with fill values. CTest unit tests:
Polaris
|
|
@mwarusz and @alicebarthel, I know you may both be busy with work related to the All-hands but I would appreciate a review on this. I had started to implement a proper treatment of the surface pressure -- initially just reading it in from the initial state if it is present. But that work ran into the problem of needing to define a fill value for SurfacePressure. Thus, I decided to work on this first. I will base my SurfacePressure work on this branch but would appreciate getting it in before too long. At the same time, I recognize that these changes touch a fair bit of code, so a good review may require some time. I am happy to explain my thinking, particularly regarding the handling of fields on edges. |
|
@brian-oneill, I also added you as a reviewer. The changes here are extensive enough that it feels like one more pair of eyes on the code would be a good idea. |
0804a22 to
fce8ffa
Compare
|
I pushed a few commits that seem to work at putting/retaining fill values in all inactive cells of state fields. I'll do some further testing and inspection after rebasing. |
This merge adds 5 constexpr fill value constants (FillValueI4, FillValueI8, FillValueR4, FillValueR8, FillValueReal) in namespace OMEGA, wrapping PIO_FILL_* from SCORPIO.
This merge adds private fillWithValue<T>() template helper (using if constexpr on T::value_type) and a call to it at the end of attachData<T>(). Arrays are now auto-filled with the declared fill value at attach time.
This merge removes all local fill value declarations and replaces references with the centralized constants.
In some cases we definitely don't want to overwrite fields we attach with fill values. Fix 2 such cases in Tracers and OceanState
We don't want fill values at every boundary edge becausee these are also valid. Instead, we want zeros except for edges between two inactive cells (e.g. both adjacent cells are below bathymetry)
Rather than assuming that the NormalVelocity read from an initial condition has correct masking, we ensure that it has fill values for inactive layers, zeros at boundary edges (both adjacent to land and to bathymetry), and leave its values unchanged for active, non-boundary layers.
We need to ensure that it is zero at active boundary edges.
Co-authored-by: Maciej Waruszewski <mwarusz@igf.fuw.edu.pl>
1812b58 to
7cad4b5
Compare
7cad4b5 to
bd1d09f
Compare
|
@mwarusz When you have a chance, let me know what you think of the changes I made. |
|
@cbegeman, thanks so much for working on this. Depending on where things stand, I can pick this up again starting on Monday. |
TestingPolaris
|
|
|
||
| // ------------------------------------------------------------------ | ||
| // Test 4: NormalVelocity has the correct 3-zone pattern after IC read | ||
| // and applyEdgeLayerMask(): |
There was a problem hiding this comment.
Consider adding additional tests for applyCellLayerMask and applyVertexLayerMask
Motivation
Omega fields declared their fill values locally in each module with inconsistent values (
-9.99e30,-9.99E+30,-999, etc.) that did not match the NetCDF-C standard. More critically, the Kokkos arrays backing those fields were never explicitly initialized: inactive ocean layers (belowMaxLayerCell) held uninitialized memory rather than a well-defined sentinel, so output files contained garbage rather than a recognizable fill value. Switching from zero-initialization to fill-value initialization also exposed a latent class of bugs where edge and boundary-layer fields assumed zero at mesh boundaries, producing corrupted tendencies when those layers were no longer zero by default.Summary
Fix fill-value handling across Omega:
components/omega/src/base/FillValues.hwith centralizedconstexprfill value constants (FillValueI4,FillValueI8,FillValueR4,FillValueR8,FillValueReal) that exactly match the NetCDF-CNC_FILL_*/ SCORPIOPIO_FILL_*values. A type-indexed variable templateFillValue<T>provides the primary interface.FillValueargument fromField::create()(and fromTracers::define()/TracerDefs.inc). The fill value is now deduced automatically from the array's element type atattachData()time.Field::attachData<T>(): a privatefillWithValue<T>()helper callsKokkos::deep_copy(InDataArray, FillValue<ValType>)and stores the fill value inFieldMeta["FillValue"]/["_FillValue"]. An optionalFillOnAttach = falseargument is provided for the three anti-pattern cases whereattachData()is called to re-point a field to an already-computed array (time-level updates inOceanStateandTracers, and the IO time-coordinate attach/assign ordering).VertCoord(zeroEdgeField,applyEdgeLayerMask) that enforce a three-zone invariant on edge fields: layers outside[MinLayerEdgeTop, MaxLayerEdgeBot]holdFillValueReal; boundary layers inside that range but outside[MinLayerEdgeBot, MaxLayerEdgeTop]hold0; active layers are unchanged.applyEdgeLayerMaskonNormalVelocityafter IC read (inOceanInit) and after eachupdateVelocityByTendstep (inTimeStepper) to keep the three-zone invariant throughout the run.zeroEdgeFieldonNormalVelocityTendandVelocityDel2Aux.Del2Edgeat the start of each tendency computation, so boundary layers show0rather thanFillValueRealas required for flux-type edge fields.VertAdv::computeVerticalPseudoVelocity(DivHU): clamp the inner edge loop toK <= MaxLayerEdgeTop(JEdge)to avoid reading fill values from edges shallower than the owning cell.FillValueTestCTest (8 MPI tasks) covering: fill constant equality withNC_FILL_*,attachDataauto-fill, inactive-layer fill verification afterVertCoord::computePressure, and the three-zoneNormalVelocityinvariant after IC initialization.Edge masking approach
Edge fields in Omega span layers
[0, NVertLayers), but the valid extent of an edge is determined by its two neighboring cells. Four per-edge layer indices (stored inVertCoord) define the three zones:MinLayerEdgeTopMinLayerEdgeBotMaxLayerEdgeTopMaxLayerEdgeBotZone 1 — fully inactive (
K < MinLayerEdgeToporK > MaxLayerEdgeBot): both neighboring cells are inactive at this layer. These layers holdFillValueRealand must never participate in any computation.Zone 2 — boundary layer (
MinLayerEdgeTop <= K < MinLayerEdgeBotorMaxLayerEdgeTop < K <= MaxLayerEdgeBot): exactly one neighbor is active. For flux-type edge fields (NormalVelocity,NormalVelocityTend,Del2Edge) the physically correct value is0because no flux crosses a solid boundary. For thickness-interpolated fields (MeanPseudoThickEdge,FluxPseudoThickEdge) the value is genuinely undefined and retainsFillValueReal.Zone 3 — active (
MinLayerEdgeBot <= K <= MaxLayerEdgeTop): both neighbors are active; the field carries a normally computed value.VertCoord::zeroEdgeFieldsets zones 1+2 to0(used at the start of each tendency step for flux fields whereattachDatafill cannot be used because the field is recomputed each step).VertCoord::applyEdgeLayerMaskimposes the full three-zone pattern (zone 1 → fill, zone 2 → 0, zone 3 → unchanged) and is applied once after IC/restart read and once after each velocity tendency update.Checklist
Testingwith the following:have been run on and indicate that are all passing.
has passed, using the Polaris
e3sm_submodules/Omegabaseline-pfor both the baseline (Polarise3sm_submodules/Omega) and the PR buildFixes #347
Fixes #409