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.
2123program 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
137152contains
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