Skip to content

Commit 3b3aec8

Browse files
committed
Add generalized axis permutation tests for diag_manager
1 parent 35cf8d7 commit 3b3aec8

5 files changed

Lines changed: 276 additions & 173 deletions

File tree

test_fms/diag_manager/Makefile.am

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ check_PROGRAMS = test_diag_manager test_diag_manager_time \
3434
check_time_pow check_time_rms check_subregional test_cell_measures test_var_masks \
3535
check_var_masks test_multiple_send_data test_diag_out_yaml test_output_every_freq \
3636
test_dm_weights test_prepend_date test_ens_runs test_diag_multi_file test_diag_attribute_add \
37-
check_new_file_freq test_zbounds_limits test_multiple_zbounds test_generalized_indicies check_generalized_indices
37+
check_new_file_freq test_zbounds_limits test_multiple_zbounds test_generalized_indices check_generalized_indices
3838

3939
# This is the source code for the test.
4040
test_output_every_freq_SOURCES = test_output_every_freq.F90
@@ -71,7 +71,7 @@ test_diag_attribute_add_SOURCES = test_diag_attribute_add.F90
7171
check_new_file_freq_SOURCES = check_new_file_freq.F90
7272
test_zbounds_limits_SOURCES = test_zbounds_limits.F90
7373
test_multiple_zbounds_SOURCES = test_multiple_zbounds.F90
74-
test_generalized_indicies_SOURCES = testing_utils.F90 test_generalized_indicies.F90
74+
test_generalized_indices_SOURCES = testing_utils.F90 test_generalized_indices.F90
7575
check_generalized_indices_SOURCES = testing_utils.F90 check_generalized_indices.F90
7676

7777
TEST_EXTENSIONS = .sh

test_fms/diag_manager/check_generalized_indices.F90

Lines changed: 36 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,27 @@
1616
!* governing permissions and limitations under the License.
1717
!***********************************************************************
1818

19-
!> @brief Checker for test_generalized_indicies output.
20-
!! Verifies swapped-axis variables match identity variables under transpose:
21-
!! var2_id(x,y) == var2_swap(y,x)
22-
!! var3_id(x,y,z) == var3_swap(y,x,z)
19+
!> @brief Checker for test_generalized_indices output.
20+
!! Verifies permuted-axis variables match identity variables under axis permutations
2321
program check_generalized_indices
2422
use fms_mod, only: fms_init, fms_end, string
23+
use testing_utils, only: check_perm
2524
use fms2_io_mod, only: FmsNetcdfFile_t, read_data, open_file, close_file, get_global_attribute
2625
use mpp_mod, only: mpp_error, FATAL, mpp_pe
27-
use platform_mod, only: r4_kind
26+
use platform_mod, only: r8_kind
2827

2928
implicit none
3029

3130
type(FmsNetcdfFile_t) :: fileobj
3231
integer :: nx, ny, nz
3332
integer :: i
3433

35-
real(kind=r4_kind), allocatable :: var2_id(:,:) ! (x,y)
36-
real(kind=r4_kind), allocatable :: var2_swap(:,:) ! (y,x)
37-
real(kind=r4_kind), allocatable :: var3_id(:,:,:) ! (x,y,z)
38-
real(kind=r4_kind), allocatable :: var3_swap(:,:,:) ! (y,x,z)
34+
real(kind=r8_kind), allocatable :: var2_id(:,:) ! (x,y)
35+
real(kind=r8_kind), allocatable :: var2_yx(:,:) ! (y,x)
36+
real(kind=r8_kind), allocatable :: var3_id(:,:,:) ! (x,y,z)
37+
real(kind=r8_kind), allocatable :: var3_zx(:,:,:) ! (z,y,x)
38+
real(kind=r8_kind), allocatable :: var3_yzx(:,:,:) ! (y,z,x)
39+
real(kind=r8_kind), allocatable :: var3_zxy(:,:,:) ! (z,x,y)
3940

4041
call fms_init()
4142

@@ -48,25 +49,35 @@ program check_generalized_indices
4849

4950
call check_global_attribute(fileobj, "test_generalized_indices")
5051

51-
allocate(var2_id(nx,ny), var2_swap(ny,nx))
52-
allocate(var3_id(nx,ny,nz), var3_swap(ny,nx,nz))
52+
allocate(var2_id(nx,ny), var2_yx(ny,nx))
53+
allocate(var3_id(nx,ny,nz), var3_zx(nz,ny,nx), var3_yzx(ny,nz,nx), var3_zxy(nz,nx,ny))
5354

5455
! Output every 6 hours over 48 hours => 8 records
5556
do i = 1, 8
56-
var2_id = -999._r4_kind
57-
var2_swap = -999._r4_kind
58-
var3_id = -999._r4_kind
59-
var3_swap = -999._r4_kind
60-
61-
print *, "Checking var2_swap vs var2_id - time_level:", string(i)
62-
call read_data(fileobj, "var2_id", var2_id, unlim_dim_level=i)
63-
call read_data(fileobj, "var2_swap", var2_swap, unlim_dim_level=i)
64-
call check_var2_relation(var2_id, var2_swap)
65-
66-
print *, "Checking var3_swap vs var3_id - time_level:", string(i)
67-
call read_data(fileobj, "var3_id", var3_id, unlim_dim_level=i)
68-
call read_data(fileobj, "var3_swap", var3_swap, unlim_dim_level=i)
69-
call check_var3_relation(var3_id, var3_swap)
57+
var2_id = -999._r8_kind
58+
var2_yx = -999._r8_kind
59+
var3_id = -999._r8_kind
60+
var3_zx = -999._r8_kind
61+
var3_yzx = -999._r8_kind
62+
var3_zxy = -999._r8_kind
63+
64+
print *, "Checking var2_yx vs var2_id - time_level:", i
65+
call read_data(fileobj, "var2_id", var2_id, unlim_dim_level=i)
66+
call read_data(fileobj, "var2_yx", var2_yx, unlim_dim_level=i)
67+
call check_perm(var2_id, var2_yx, [2,1])
68+
69+
print *, "Checking var3_zx vs var3_id - time_level:", i
70+
call read_data(fileobj, "var3_id", var3_id, unlim_dim_level=i)
71+
call read_data(fileobj, "var3_zx", var3_zx, unlim_dim_level=i)
72+
call check_perm(var3_id, var3_zx, [3,2,1])
73+
74+
print *, "Checking var3_yzx vs var3_id - time_level:", i
75+
call read_data(fileobj, "var3_yzx", var3_yzx, unlim_dim_level=i)
76+
call check_perm(var3_id, var3_yzx, [2,3,1])
77+
78+
print *, "Checking var3_zxy vs var3_id - time_level:", i
79+
call read_data(fileobj, "var3_zxy", var3_zxy, unlim_dim_level=i)
80+
call check_perm(var3_id, var3_zxy, [3,1,2])
7081
enddo
7182

7283
call close_file(fileobj)
@@ -85,49 +96,4 @@ subroutine check_global_attribute(fileobj, expected_title)
8596
call mpp_error(FATAL, "Global attribute 'title' not expected value.")
8697
endif
8798
end subroutine check_global_attribute
88-
89-
subroutine check_var2_relation(v_id, v_sw)
90-
real(kind=r4_kind), intent(in) :: v_id(:,:) ! (x,y)
91-
real(kind=r4_kind), intent(in) :: v_sw(:,:) ! (y,x)
92-
93-
integer :: x, y
94-
95-
if (size(v_id,1) /= size(v_sw,2) .or. size(v_id,2) /= size(v_sw,1)) then
96-
call mpp_error(FATAL, "check_var2_relation: dimension mismatch between var2_id and var2_swap")
97-
endif
98-
99-
do x = 1, size(v_id,1)
100-
do y = 1, size(v_id,2)
101-
if (abs(v_id(x,y) - v_sw(y,x)) > 0) then
102-
print *, mpp_pe(), "var2 mismatch at (x,y)=", x, y, " id=", v_id(x,y), " swap(y,x)=", v_sw(y,x)
103-
call mpp_error(FATAL, "check_var2_relation: var2_swap != transpose(var2_id)")
104-
endif
105-
enddo
106-
enddo
107-
end subroutine check_var2_relation
108-
109-
subroutine check_var3_relation(v_id, v_sw)
110-
real(kind=r4_kind), intent(in) :: v_id(:,:,:) ! (x,y,z)
111-
real(kind=r4_kind), intent(in) :: v_sw(:,:,:) ! (y,x,z)
112-
113-
integer :: x, y, z
114-
115-
if (size(v_id,1) /= size(v_sw,2) .or. size(v_id,2) /= size(v_sw,1) .or. size(v_id,3) /= size(v_sw,3)) then
116-
call mpp_error(FATAL, "check_var3_relation: dimension mismatch between var3_id and var3_swap")
117-
endif
118-
119-
do x = 1, size(v_id,1)
120-
do y = 1, size(v_id,2)
121-
do z = 1, size(v_id,3)
122-
if (abs(v_id(x,y,z) - v_sw(y,x,z)) > 0) then
123-
print *, mpp_pe(), "var3 mismatch at (x,y,z)=", x, y, z, &
124-
" id=", v_id(x,y,z), " swap(y,x,z)=", v_sw(y,x,z)
125-
call mpp_error(FATAL, "check_var3_relation: var3_swap != var3_id with x/y swapped")
126-
endif
127-
enddo
128-
enddo
129-
enddo
130-
end subroutine check_var3_relation
131-
13299
end program check_generalized_indices
133-

test_fms/diag_manager/test_generalized_indicies.F90 renamed to test_fms/diag_manager/test_generalized_indices.F90

Lines changed: 102 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
!* governing permissions and limitations under the License.
1717
!***********************************************************************
1818

19-
!> @brief Test generalized axis permutations (x/y for now) for send_data
19+
!> @brief Test generalized axis permutations for send_data.
20+
!! Applies predefined permutations to canonical (x,y,z,w) storage
21+
!! and verifies consistency between data layout and axis metadata.
2022
!! Assumes default configuration parameters: test_normal + no_mask.
2123
program test_generalized_indices
2224
use fms_mod, only: fms_init, fms_end
23-
use testing_utils, only: allocate_buffer
25+
use testing_utils, only: allocate_buffer, permute
2426
use platform_mod, only: r8_kind
2527
use mpp_mod, only: FATAL, mpp_error, mpp_npes, mpp_pe, mpp_root_pe
2628
use time_manager_mod, only: time_type, set_calendar_type, set_date, JULIAN, set_time, OPERATOR(+)
@@ -47,9 +49,21 @@ program test_generalized_indices
4749
real(r8_kind), allocatable :: cdata(:,:,:,:) ! canonical storage: (x,y,z,w)
4850

4951
! Permutation test
50-
integer :: p_id(3), p_swap(3)
51-
integer :: id_var2_id, id_var2_swap
52-
integer :: id_var3_id, id_var3_swap
52+
integer, parameter :: LAYOUT_XY = 1
53+
integer, parameter :: LAYOUT_YX = 2
54+
integer, parameter :: LAYOUT_ZX = 3
55+
integer, parameter :: LAYOUT_YZX = 4
56+
integer, parameter :: LAYOUT_ZXY = 5
57+
integer, parameter :: PERM_TABLE(3,5) = reshape([ &
58+
1,2,3, & ! XY (identity)
59+
2,1,3, & ! YX
60+
3,2,1, & ! ZX
61+
2,3,1, & ! YZX
62+
3,1,2 & ! ZXY
63+
], [3,5])
64+
65+
integer :: id_var2_id, id_var2_yx
66+
integer :: id_var3_id, id_var3_zx, id_var3_yzx, id_var3_zxy
5367

5468
call fms_init
5569
call set_calendar_type(JULIAN)
@@ -88,29 +102,26 @@ program test_generalized_indices
88102

89103
missing_value = -666._r8_kind
90104

91-
! Define permutations: identity (x,y,z) and swap (y,x,z)
92-
p_id = [1,2,3]
93-
p_swap = [2,1,3]
94-
95105
! Register permuted diagnostic fields ONCE
96-
id_var2_id = register_diag_field('ocn_mod', 'var2_id', (/axis(p_id(1)), axis(p_id(2))/), Time, 'Var2d id', &
97-
'mullions', missing_value=missing_value)
98-
id_var2_swap = register_diag_field('ocn_mod', 'var2_swap', (/axis(p_swap(1)), axis(p_swap(2))/), Time, 'Var2d swap', &
99-
'mullions', missing_value=missing_value)
106+
id_var2_id = register_perm_diag_field('var2_id', 'Var2d id', axis(1:2), LAYOUT_XY)
107+
id_var3_id = register_perm_diag_field('var3_id', 'Var3d id', axis(1:3), LAYOUT_XY)
108+
109+
id_var2_yx = register_perm_diag_field('var2_yx', 'Var2d yx', axis(1:2), LAYOUT_YX)
110+
id_var3_zx = register_perm_diag_field('var3_zx', 'Var3d zx', axis(1:3), LAYOUT_ZX)
100111

101-
id_var3_id = register_diag_field('ocn_mod', 'var3_id', (/axis(p_id(1)), axis(p_id(2)), axis(p_id(3))/), &
102-
Time, 'Var3d id', 'mullions', missing_value=missing_value)
103-
id_var3_swap = register_diag_field('ocn_mod', 'var3_swap', (/axis(p_swap(1)), axis(p_swap(2)), axis(p_swap(3))/), &
104-
Time, 'Var3d swap', 'mullions', missing_value=missing_value)
112+
id_var3_yzx = register_perm_diag_field('var3_yzx', 'Var3d yzx', axis(1:3), LAYOUT_YZX)
113+
id_var3_zxy = register_perm_diag_field('var3_zxy', 'Var3d zxy', axis(1:3), LAYOUT_ZXY)
105114

106115
if (mpp_pe() == mpp_root_pe()) then
107116
print *, "Testing generalized indices in default mode (test_normal + no_mask)"
108117
print *, " canonical storage is (x,y,z,w)"
109118
print *, " sending:"
110-
print *, " var2_id with axes (x,y)"
111-
print *, " var2_swap with axes (y,x)"
112-
print *, " var3_id with axes (x,y,z)"
113-
print *, " var3_swap with axes (y,x,z)"
119+
print *, " var2_id with axes (x,y)"
120+
print *, " var2_yx with axes (y,x)"
121+
print *, " var3_id with axes (x,y,z)"
122+
print *, " var3_zx with axes (z,y,x)"
123+
print *, " var3_yzx with axes (y,z,x)"
124+
print *, " var3_zxy with axes (z,x,y)"
114125
end if
115126

116127
call diag_manager_set_time_end(set_date(2,1,3,0,0,0))
@@ -120,12 +131,16 @@ program test_generalized_indices
120131
call set_buffer(cdata, i)
121132

122133
! Identity: axes (x,y) / (x,y,z) with canonical storage
123-
call send_var2_perm(id_var2_id, cdata, p_id, Time)
124-
call send_var3_perm(id_var3_id, cdata, p_id, Time)
134+
call send_perm_data(id_var2_id, cdata, PERM_TABLE(1:2, LAYOUT_XY), Time)
135+
call send_perm_data(id_var3_id, cdata, PERM_TABLE(1:3, LAYOUT_XY), Time)
136+
137+
! Swap: axes (y,x) / (z,y,x) while canonical storage remains (x,y,...) -> pack to temp and send
138+
call send_perm_data(id_var2_yx, cdata, PERM_TABLE(1:2, LAYOUT_YX), Time)
139+
call send_perm_data(id_var3_zx, cdata, PERM_TABLE(1:3, LAYOUT_ZX), Time)
125140

126-
! Swap: axes (y,x) / (y,x,z) while canonical storage remains (x,y,...) -> pack to temp and send
127-
call send_var2_perm(id_var2_swap, cdata, p_swap, Time)
128-
call send_var3_perm(id_var3_swap, cdata, p_swap, Time)
141+
! Cyclic: axes (y,z,x) / (z,x,y) while canonical storage remains (x, y, ...) -> pack to temp and send
142+
call send_perm_data(id_var3_yzx, cdata, PERM_TABLE(1:3, LAYOUT_YZX), Time)
143+
call send_perm_data(id_var3_zxy, cdata, PERM_TABLE(1:3, LAYOUT_ZXY), Time)
129144

130145
call diag_send_complete(Time_step)
131146
call diag_send_complete(Time_step)
@@ -136,63 +151,75 @@ program test_generalized_indices
136151

137152
contains
138153

139-
subroutine send_var2_perm(id_field, buf, p, Time_in)
140-
integer, intent(in) :: id_field
141-
real(r8_kind), intent(in) :: buf(:,:,:,:) ! canonical (x,y,z,w)
142-
integer, intent(in) :: p(3)
143-
type(time_type), intent(in) :: Time_in
144-
145-
logical :: used_local
146-
real(r8_kind), allocatable :: tmp2(:,:)
147-
148-
! Support only identity (1,2,*) and xy-swap (2,1,*) for 2D
149-
if (p(1)==1 .and. p(2)==2) then
150-
used_local = send_data(id_field, buf(:,:,1,1), Time_in)
151-
else if (p(1)==2 .and. p(2)==1) then
152-
allocate(tmp2(size(buf,2), size(buf,1)))
153-
tmp2 = transpose(buf(:,:,1,1))
154-
used_local = send_data(id_field, tmp2, Time_in)
155-
deallocate(tmp2)
156-
else
157-
call mpp_error(FATAL, 'send_var2_perm: only p=(1,2,*) or (2,1,*) implemented')
158-
end if
159-
end subroutine send_var2_perm
160-
161-
162-
subroutine send_var3_perm(id_field, buf, p, Time_in)
154+
!> @brief Apply a predefined permutation to an axis array.
155+
!> Maps canonical axis ordering to a permuted layout using PERM_TABLE.
156+
!> Supports rank-2 and rank-3 axis subsets.
157+
subroutine permute_axis(axis_in, perm_id, axis_out)
158+
integer, intent(in) :: axis_in(:)
159+
integer, intent(in) :: perm_id
160+
integer, intent(out) :: axis_out(:)
161+
162+
integer :: order(3)
163+
164+
order = PERM_TABLE(:, perm_id)
165+
166+
if (any(order(1:size(axis_out)) > size(axis_in))) then
167+
call mpp_error(FATAL, "permute_axis: invalid permutation for given rank")
168+
endif
169+
170+
axis_out = axis_in(order(1:size(axis_out)))
171+
end subroutine permute_axis
172+
173+
!> @brief Register a diagnostic field with permuted axes.
174+
!> Applies axis permutation before calling register_diag_field.
175+
function register_perm_diag_field(var_name, long_name, axis, perm_id) result(id_var)
176+
character(len=*), intent(in) :: var_name, long_name
177+
integer, intent(in) :: axis(:)
178+
integer, intent(in) :: perm_id
179+
180+
integer :: id_var
181+
integer, allocatable :: axis_perm(:)
182+
183+
allocate(axis_perm(size(axis)))
184+
call permute_axis(axis, perm_id, axis_perm)
185+
id_var = register_diag_field('ocn_mod', var_name, axis_perm, Time, long_name, &
186+
'mullions', missing_value=missing_value)
187+
deallocate(axis_perm)
188+
end function register_perm_diag_field
189+
190+
!> @brief Send data with optional axis permutation.
191+
!> Applies 2D or 3D permutation to canonical (x,y,z,w) buffers before send_data.
192+
!> Skips permutation for identity mappings.
193+
subroutine send_perm_data(id_field, buf, order, Time_in)
163194
integer, intent(in) :: id_field
164195
real(r8_kind), intent(in) :: buf(:,:,:,:) ! canonical (x,y,z,w)
165-
integer, intent(in) :: p(3)
196+
integer, intent(in) :: order(:)
166197
type(time_type), intent(in) :: Time_in
167198

168199
logical :: used_local
169-
integer :: nxloc, nyloc, nzloc, k
170-
real(r8_kind), allocatable :: tmp3(:,:,:)
171-
172-
! For now, support only keeping z as z
173-
if (p(3) /= 3) call mpp_error(FATAL, 'send_var3_perm: only permutations with p(3)=3 implemented')
174-
175-
if (p(1)==1 .and. p(2)==2) then
176-
used_local = send_data(id_field, buf(:,:,:,1), Time_in)
177-
178-
else if (p(1)==2 .and. p(2)==1) then
179-
nxloc = size(buf,1)
180-
nyloc = size(buf,2)
181-
nzloc = size(buf,3)
182-
183-
allocate(tmp3(nyloc, nxloc, nzloc))
184-
do k = 1, nzloc
185-
tmp3(:,:,k) = transpose(buf(:,:,k,1))
186-
end do
187-
188-
used_local = send_data(id_field, tmp3, Time_in)
189-
deallocate(tmp3)
200+
real(r8_kind), allocatable :: tmp2(:,:), tmp3(:,:,:)
201+
202+
if (size(order) == 2) then
203+
if (all(order == [1,2])) then
204+
used_local = send_data(id_field, buf(:,:,1,1), Time_in)
205+
else
206+
tmp2 = permute(buf(:,:,1,1), order)
207+
used_local = send_data(id_field, tmp2, Time_in)
208+
endif
209+
210+
else if (size(order) == 3) then
211+
if (all(order == [1,2,3])) then
212+
used_local = send_data(id_field, buf(:,:,:,1), Time_in)
213+
else
214+
tmp3 = permute(buf(:,:,:,1), order)
215+
used_local = send_data(id_field, tmp3, Time_in)
216+
endif
190217

191218
else
192-
call mpp_error(FATAL, 'send_var3_perm: only p=(1,2,3) or (2,1,3) implemented')
193-
end if
194-
end subroutine send_var3_perm
219+
call mpp_error(FATAL, "send_var_perm: unsupported permutation rank")
195220

221+
endif
222+
end subroutine send_perm_data
196223

197224
!> @brief initialized the buffer based on the starting/ending indices
198225
subroutine init_buffer(buffer, is, ie, js, je, nhalo)
@@ -215,7 +242,6 @@ subroutine init_buffer(buffer, is, ie, js, je, nhalo)
215242
end do
216243
end subroutine init_buffer
217244

218-
219245
!> @brief Set the buffer based on the time_index
220246
subroutine set_buffer(buffer, time_index)
221247
real(r8_kind), intent(inout) :: buffer(:,:,:,:)

0 commit comments

Comments
 (0)