Skip to content

Commit 22155f3

Browse files
authored
Add global field fill default override (#4690) (#4701)
* Add global field fill default override (#4690) Adds a singleton module FieldFillDefault with lazy initialization that holds the default fill values (sNaN) for R4 and R8 fields. MaplFramework gains a public initialize_field_fill_defaults procedure callable at startup to override the defaults via YAML or Fortran args. Includes 8 new pFUnit tests in Test_FieldFill.pf. * Fix split _ASSERT macros for gfortran CPP * Apply suggestions from code review Co-authored-by: Tom Clune <thomas.l.clune@nasa.gov>
1 parent 01675c4 commit 22155f3

6 files changed

Lines changed: 362 additions & 14 deletions

File tree

field/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ set(srcs
1111
FieldUnits.F90
1212
FieldCondensedArray.F90
1313
FieldCondensedArray_private.F90
14+
FieldFillDefault.F90
1415
FieldFill.F90
1516
FieldCreate.F90
1617
FieldDelta.F90

field/FieldFill.F90

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "MAPL.h"
22

33
module mapl3g_FieldFill
4+
use mapl3g_FieldFillDefault
45
use mapl_FieldPointerUtilities, only: assign_fptr
56
use mapl_ErrorHandling
67
use esmf
@@ -16,18 +17,15 @@ module mapl3g_FieldFill
1617
contains
1718

1819
subroutine field_fill(field, rc)
19-
use, intrinsic :: ieee_arithmetic, only: ieee_value, ieee_signaling_nan
20-
use, intrinsic :: iso_fortran_env, only: REAL32, REAL64, INT32, INT64
20+
use, intrinsic :: iso_fortran_env, only: INT32, INT64
2121
type(ESMF_Field), intent(inout) :: field
2222
integer, optional, intent(out) :: rc
2323

24-
real(REAL32), pointer :: ptr_r4(:)
25-
real(REAL64), pointer :: ptr_r8(:)
24+
real(ESMF_KIND_R4), pointer :: ptr_r4(:)
25+
real(ESMF_KIND_R8), pointer :: ptr_r8(:)
2626
integer(INT32), pointer :: ptr_i4(:)
2727
integer(INT64), pointer :: ptr_i8(:)
2828
integer :: status
29-
real(REAL32) :: snan_r4
30-
real(REAL64) :: snan_r8
3129
type(ESMF_FieldStatus_Flag) :: field_status
3230
type(ESMF_TypeKind_Flag) :: typekind
3331

@@ -39,13 +37,11 @@ subroutine field_fill(field, rc)
3937
call ESMF_FieldGet(field, typekind=typekind, _RC)
4038

4139
if (typekind == ESMF_TYPEKIND_R4) then
42-
snan_r4 = ieee_value(snan_r4, ieee_signaling_nan)
4340
call assign_fptr(field, ptr_r4, _RC)
44-
ptr_r4 = snan_r4
41+
ptr_r4 = get_field_fill_default_r4()
4542
else if (typekind == ESMF_TYPEKIND_R8) then
46-
snan_r8 = ieee_value(snan_r8, ieee_signaling_nan)
4743
call assign_fptr(field, ptr_r8, _RC)
48-
ptr_r8 = snan_r8
44+
ptr_r8 = get_field_fill_default_r8()
4945
else if (typekind == ESMF_TYPEKIND_I4) then
5046
call assign_fptr(field, ptr_i4, _RC)
5147
ptr_i4 = -huge(1_INT32)

field/FieldFillDefault.F90

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
module mapl3g_FieldFillDefault
2+
use iso_fortran_env, only: REAL32, REAL64
3+
implicit none(type, external)
4+
private
5+
6+
public :: set_field_fill_defaults
7+
public :: get_field_fill_default_r4
8+
public :: get_field_fill_default_r8
9+
public :: initialize_field_fill_defaults
10+
public :: reset_field_fill_defaults
11+
12+
! Module-level singleton fill values. A separate flag tracks whether the
13+
! module has been explicitly initialized, so that get_field_fill_default_*
14+
! can lazily self-initialize to sNaN on first use. This allows FieldFill
15+
! to work correctly even in test contexts that do not go through MaplFramework.
16+
logical :: is_initialized = .false.
17+
real(REAL32) :: fill_default_r4
18+
real(REAL64) :: fill_default_r8
19+
20+
contains
21+
22+
! Sets both defaults to sNaN. Called explicitly by MaplFramework during
23+
! startup, and implicitly (lazily) on first use of the getters.
24+
! Individual values may subsequently be overridden via set_field_fill_defaults().
25+
subroutine initialize_field_fill_defaults()
26+
use, intrinsic :: ieee_arithmetic, only: ieee_value, ieee_signaling_nan
27+
fill_default_r4 = ieee_value(fill_default_r4, ieee_signaling_nan)
28+
fill_default_r8 = ieee_value(fill_default_r8, ieee_signaling_nan)
29+
is_initialized = .true.
30+
end subroutine initialize_field_fill_defaults
31+
32+
! Sets one or both singleton fill values. Arguments are allocatable so
33+
! callers can pass unallocated variables to leave a typekind unchanged.
34+
subroutine set_field_fill_defaults(r4, r8)
35+
real(REAL32), allocatable, optional, intent(in) :: r4
36+
real(REAL64), allocatable, optional, intent(in) :: r8
37+
if (.not. is_initialized) call initialize_field_fill_defaults()
38+
if (present(r4)) then
39+
if (allocated(r4)) fill_default_r4 = r4
40+
end if
41+
if (present(r8)) then
42+
if (allocated(r8)) fill_default_r8 = r8
43+
end if
44+
end subroutine set_field_fill_defaults
45+
46+
function get_field_fill_default_r4() result(value)
47+
real(REAL32) :: value
48+
if (.not. is_initialized) call initialize_field_fill_defaults()
49+
value = fill_default_r4
50+
end function get_field_fill_default_r4
51+
52+
function get_field_fill_default_r8() result(value)
53+
real(REAL64) :: value
54+
if (.not. is_initialized) call initialize_field_fill_defaults()
55+
value = fill_default_r8
56+
end function get_field_fill_default_r8
57+
58+
! Restores both defaults to sNaN. Primarily intended for use in unit tests.
59+
subroutine reset_field_fill_defaults()
60+
call initialize_field_fill_defaults()
61+
end subroutine reset_field_fill_defaults
62+
63+
end module mapl3g_FieldFillDefault

field/tests/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
set(MODULE_DIRECTORY "${esma_include}/MAPL.field/tests")
22

33
add_pfunit_ctest(MAPL.field.test_fieldcreate
4-
TEST_SOURCES Test_FieldCreate.pf
4+
TEST_SOURCES Test_FieldCreate.pf Test_FieldFill.pf
55
LINK_LIBRARIES MAPL.field MAPL.pfunit
66
EXTRA_INITIALIZE Initialize
77
EXTRA_USE MAPL_pFUnit_Initialize

field/tests/Test_FieldFill.pf

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
#include "MAPL_TestErr.h"
2+
#include "unused_dummy.H"
3+
4+
module Test_FieldFill
5+
use mapl3g_FieldFillDefault
6+
use mapl3g_FieldCreate, only: MAPL_FieldEmptyComplete
7+
use mapl_FieldPointerUtilities, only: assign_fptr
8+
use funit
9+
use ESMF_TestMethod_mod
10+
use esmf
11+
use, intrinsic :: ieee_arithmetic, only: ieee_is_nan
12+
13+
implicit none(type, external)
14+
15+
contains
16+
17+
@Before
18+
subroutine setUp(this)
19+
class(ESMF_TestMethod), intent(inout) :: this
20+
! Ensure singletons are set to sNaN before each test.
21+
call initialize_field_fill_defaults()
22+
_UNUSED_DUMMY(this)
23+
end subroutine setUp
24+
25+
@After
26+
subroutine tearDown(this)
27+
class(ESMF_TestMethod), intent(inout) :: this
28+
! Restore sNaN after each test to avoid cross-test contamination.
29+
call reset_field_fill_defaults()
30+
_UNUSED_DUMMY(this)
31+
end subroutine tearDown
32+
33+
! Helper: create a simple 2D R4 field and return a pointer to its data.
34+
subroutine make_r4_field(field, ptr, rc)
35+
type(ESMF_Field), intent(out) :: field
36+
real(ESMF_KIND_R4), pointer, intent(out) :: ptr(:)
37+
integer, optional, intent(out) :: rc
38+
39+
type(ESMF_Grid) :: grid
40+
type(ESMF_Geom) :: geom
41+
integer :: status
42+
43+
grid = ESMF_GridCreateNoPeriDim(maxIndex=[4, 4], _RC)
44+
geom = ESMF_GeomCreate(grid, _RC)
45+
field = ESMF_FieldEmptyCreate(_RC)
46+
call ESMF_FieldEmptySet(field, geom=geom, _RC)
47+
call MAPL_FieldEmptyComplete(field, typekind=ESMF_TYPEKIND_R4, _RC)
48+
call assign_fptr(field, ptr, _RC)
49+
call ESMF_GridDestroy(grid, _RC)
50+
call ESMF_GeomDestroy(geom, _RC)
51+
_RETURN(_SUCCESS)
52+
end subroutine make_r4_field
53+
54+
! Helper: create a simple 2D R8 field and return a pointer to its data.
55+
subroutine make_r8_field(field, ptr, rc)
56+
type(ESMF_Field), intent(out) :: field
57+
real(ESMF_KIND_R8), pointer, intent(out) :: ptr(:)
58+
integer, optional, intent(out) :: rc
59+
60+
type(ESMF_Grid) :: grid
61+
type(ESMF_Geom) :: geom
62+
integer :: status
63+
64+
grid = ESMF_GridCreateNoPeriDim(maxIndex=[4, 4], _RC)
65+
geom = ESMF_GeomCreate(grid, _RC)
66+
field = ESMF_FieldEmptyCreate(_RC)
67+
call ESMF_FieldEmptySet(field, geom=geom, _RC)
68+
call MAPL_FieldEmptyComplete(field, typekind=ESMF_TYPEKIND_R8, _RC)
69+
call assign_fptr(field, ptr, _RC)
70+
call ESMF_GridDestroy(grid, _RC)
71+
call ESMF_GeomDestroy(geom, _RC)
72+
_RETURN(_SUCCESS)
73+
end subroutine make_r8_field
74+
75+
! Without any global override, R4 fields must be filled with sNaN.
76+
@test(type=ESMF_TestMethod, npes=[1])
77+
subroutine test_default_fill_r4_is_nan(this)
78+
class(ESMF_TestMethod), intent(inout) :: this
79+
80+
type(ESMF_Field) :: field
81+
real(ESMF_KIND_R4), pointer :: ptr(:)
82+
integer :: status
83+
84+
call make_r4_field(field, ptr, _RC)
85+
@assertTrue(all(ieee_is_nan(ptr)), "R4 field should be filled with NaN by default")
86+
87+
call ESMF_FieldDestroy(field, nogarbage=.true., _RC)
88+
_UNUSED_DUMMY(this)
89+
end subroutine test_default_fill_r4_is_nan
90+
91+
! Without any global override, R8 fields must be filled with sNaN.
92+
@test(type=ESMF_TestMethod, npes=[1])
93+
subroutine test_default_fill_r8_is_nan(this)
94+
class(ESMF_TestMethod), intent(inout) :: this
95+
96+
type(ESMF_Field) :: field
97+
real(ESMF_KIND_R8), pointer :: ptr(:)
98+
integer :: status
99+
100+
call make_r8_field(field, ptr, _RC)
101+
@assertTrue(all(ieee_is_nan(ptr)), "R8 field should be filled with NaN by default")
102+
103+
call ESMF_FieldDestroy(field, nogarbage=.true., _RC)
104+
_UNUSED_DUMMY(this)
105+
end subroutine test_default_fill_r8_is_nan
106+
107+
! After setting a global R4 override, new R4 fields get that value instead of sNaN.
108+
@test(type=ESMF_TestMethod, npes=[1])
109+
subroutine test_override_fill_r4(this)
110+
class(ESMF_TestMethod), intent(inout) :: this
111+
112+
type(ESMF_Field) :: field
113+
real(ESMF_KIND_R4), pointer :: ptr(:)
114+
real(ESMF_KIND_R4), parameter :: FILL = 0.0_ESMF_KIND_R4
115+
real(ESMF_KIND_R4), allocatable :: fill_r4
116+
integer :: status
117+
118+
allocate(fill_r4, source=FILL)
119+
call set_field_fill_defaults(r4=fill_r4)
120+
call make_r4_field(field, ptr, _RC)
121+
@assertTrue(all(ptr == FILL), "R4 field should be filled with override value")
122+
123+
call ESMF_FieldDestroy(field, nogarbage=.true., _RC)
124+
_UNUSED_DUMMY(this)
125+
end subroutine test_override_fill_r4
126+
127+
! After setting a global R8 override, new R8 fields get that value instead of sNaN.
128+
@test(type=ESMF_TestMethod, npes=[1])
129+
subroutine test_override_fill_r8(this)
130+
class(ESMF_TestMethod), intent(inout) :: this
131+
132+
type(ESMF_Field) :: field
133+
real(ESMF_KIND_R8), pointer :: ptr(:)
134+
real(ESMF_KIND_R8), parameter :: FILL = 0.0_ESMF_KIND_R8
135+
real(ESMF_KIND_R8), allocatable :: fill_r8
136+
integer :: status
137+
138+
allocate(fill_r8, source=FILL)
139+
call set_field_fill_defaults(r8=fill_r8)
140+
call make_r8_field(field, ptr, _RC)
141+
@assertTrue(all(ptr == FILL), "R8 field should be filled with override value")
142+
143+
call ESMF_FieldDestroy(field, nogarbage=.true., _RC)
144+
_UNUSED_DUMMY(this)
145+
end subroutine test_override_fill_r8
146+
147+
! After reset, fills return to sNaN.
148+
@test(type=ESMF_TestMethod, npes=[1])
149+
subroutine test_reset_restores_nan(this)
150+
class(ESMF_TestMethod), intent(inout) :: this
151+
152+
type(ESMF_Field) :: field
153+
real(ESMF_KIND_R4), pointer :: ptr(:)
154+
real(ESMF_KIND_R4), allocatable :: fill_r4
155+
integer :: status
156+
157+
allocate(fill_r4, source=0.0_ESMF_KIND_R4)
158+
call set_field_fill_defaults(r4=fill_r4)
159+
call reset_field_fill_defaults()
160+
call make_r4_field(field, ptr, _RC)
161+
@assertTrue(all(ieee_is_nan(ptr)), "R4 field should revert to NaN after reset")
162+
163+
call ESMF_FieldDestroy(field, nogarbage=.true., _RC)
164+
_UNUSED_DUMMY(this)
165+
end subroutine test_reset_restores_nan
166+
167+
! Getters return NaN when no override has been set.
168+
@test(type=ESMF_TestMethod, npes=[1])
169+
subroutine test_getter_returns_nan_when_unset(this)
170+
class(ESMF_TestMethod), intent(inout) :: this
171+
172+
@assertTrue(ieee_is_nan(get_field_fill_default_r4()), "R4 getter should return NaN when no override is set")
173+
@assertTrue(ieee_is_nan(get_field_fill_default_r8()), "R8 getter should return NaN when no override is set")
174+
175+
_UNUSED_DUMMY(this)
176+
end subroutine test_getter_returns_nan_when_unset
177+
178+
! Getters return the override value after set_field_fill_defaults() is called.
179+
@test(type=ESMF_TestMethod, npes=[1])
180+
subroutine test_getter_returns_override_value(this)
181+
class(ESMF_TestMethod), intent(inout) :: this
182+
183+
real(ESMF_KIND_R4), allocatable :: fill_r4
184+
real(ESMF_KIND_R8), allocatable :: fill_r8
185+
186+
allocate(fill_r4, source=42.0_ESMF_KIND_R4)
187+
allocate(fill_r8, source=99.0_ESMF_KIND_R8)
188+
call set_field_fill_defaults(r4=fill_r4, r8=fill_r8)
189+
190+
@assertEqual(get_field_fill_default_r4(), 42.0_ESMF_KIND_R4, "R4 getter should return the set value")
191+
@assertEqual(get_field_fill_default_r8(), 99.0_ESMF_KIND_R8, "R8 getter should return the set value")
192+
193+
_UNUSED_DUMMY(this)
194+
end subroutine test_getter_returns_override_value
195+
196+
! R4 and R8 overrides are independent — setting one does not affect the other.
197+
@test(type=ESMF_TestMethod, npes=[1])
198+
subroutine test_r4_and_r8_overrides_are_independent(this)
199+
class(ESMF_TestMethod), intent(inout) :: this
200+
201+
type(ESMF_Field) :: field_r4, field_r8
202+
real(ESMF_KIND_R4), pointer :: ptr_r4(:)
203+
real(ESMF_KIND_R8), pointer :: ptr_r8(:)
204+
real(ESMF_KIND_R4), allocatable :: fill_r4
205+
integer :: status
206+
207+
! Only set R4 override; R8 should still be sNaN.
208+
allocate(fill_r4, source=1.0_ESMF_KIND_R4)
209+
call set_field_fill_defaults(r4=fill_r4)
210+
211+
call make_r4_field(field_r4, ptr_r4, _RC)
212+
@assertTrue(all(ptr_r4 == 1.0_ESMF_KIND_R4), "R4 field should use override")
213+
214+
call make_r8_field(field_r8, ptr_r8, _RC)
215+
@assertTrue(all(ieee_is_nan(ptr_r8)), "R8 field should still be NaN when only R4 override is set")
216+
217+
call ESMF_FieldDestroy(field_r4, nogarbage=.true., _RC)
218+
call ESMF_FieldDestroy(field_r8, nogarbage=.true., _RC)
219+
_UNUSED_DUMMY(this)
220+
end subroutine test_r4_and_r8_overrides_are_independent
221+
222+
end module Test_FieldFill

0 commit comments

Comments
 (0)