FMS  2025.04
Flexible Modeling System
diag_axis.F90
1 !***********************************************************************
2 !* Apache License 2.0
3 !*
4 !* This file is part of the GFDL Flexible Modeling System (FMS).
5 !*
6 !* Licensed under the Apache License, Version 2.0 (the "License");
7 !* you may not use this file except in compliance with the License.
8 !* You may obtain a copy of the License at
9 !*
10 !* http://www.apache.org/licenses/LICENSE-2.0
11 !*
12 !* FMS is distributed in the hope that it will be useful, but WITHOUT
13 !* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied;
14 !* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15 !* PARTICULAR PURPOSE. See the License for the specific language
16 !* governing permissions and limitations under the License.
17 !***********************************************************************
18 !> @defgroup diag_axis_mod diag_axis_mod
19 !> @ingroup diag_manager
20 !> @brief An integral part of @ref diag_manager_mod. It helps to create axis IDs
21 !! that are used in @ref register_diag_field.
22 !!
23 !> @author Seth Underwood
24 !!
25 !! Users first create axis ID by calling diag_axis_init, then use this axis ID in
26 !! register_diag_field.
27 
28 !> @addtogroup diag_axis_mod
29 !> @{
30 MODULE diag_axis_mod
31 use platform_mod
32 
33  USE mpp_domains_mod, ONLY: domainug, domain1d, domain2d, mpp_get_compute_domain,&
34  & mpp_get_domain_components, null_domain1d, null_domain2d, null_domainug,&
35  & north, east, center, &
37  USE fms_mod, ONLY: error_mesg, write_version_number, lowercase, uppercase,&
38  & fms_error_handler, fatal, note
39  USE diag_data_mod, ONLY: diag_axis_type, max_subaxes, max_axes,&
40  & max_num_axis_sets, max_axis_attributes, debug_diag_manager,&
41  & first_send_data_call, diag_atttype, use_modern_diag
42  use fms_diag_object_mod, only:fms_diag_object
43  USE netcdf, ONLY: nf90_int, nf90_float, nf90_char
44 
45  IMPLICIT NONE
46 
47  PRIVATE
55  & north, east, center, diag_axis_type
56 
57  ! Include variable "version" to be written to log file
58 #include<file_version.h>
59 
60 !----------
61  integer(I4_KIND),parameter,public :: DIAG_AXIS_NODOMAIN = 0 !< For unstructured grid support
62  integer(I4_KIND),parameter,public :: diag_axis_2ddomain = 1 !< For unstructured grid support
63  integer(I4_KIND),parameter,public :: diag_axis_ugdomain = 2 !< For unstructured grid support
64 !----------
65 
66  INTEGER, DIMENSION(:), ALLOCATABLE :: num_subaxes !< counter of number of axes defined
67  INTEGER :: num_def_axes = 0
68 
69  CHARACTER(len=128), DIMENSION(:), ALLOCATABLE, SAVE :: axis_sets !< storage for axis set names
70  INTEGER :: num_axis_sets = 0
71 
72  TYPE(diag_axis_type), ALLOCATABLE, SAVE :: axes(:) !< global storage for all defined axes
73  LOGICAL :: module_is_initialized = .false.
74 
75  !> @}
76 
77  !> @brief Add an arbitrary attribute and value to the diagnostic axis.
78  !!
79  !> Any number of attributes can be added to a given axis. All attribute addition must
80  !! be done before first <TT>send_data</TT> call.<br>
81  !!
82  !! If a real or integer attribute is already defined, a FATAL error will be called.
83  !! If a character attribute is already defined, then it will be prepended to the
84  !! existing attribute value.
85  !! <br>Example usage:
86  !! @code{.F90} call diag_axis_add_attribute(diag_axis_id, att_name, att_value) @endcode
87  !> @ingroup diag_axis_mod
89  MODULE PROCEDURE diag_axis_add_attribute_scalar_r
90  MODULE PROCEDURE diag_axis_add_attribute_scalar_i
91  MODULE PROCEDURE diag_axis_add_attribute_scalar_c
92  MODULE PROCEDURE diag_axis_add_attribute_r1d
93  MODULE PROCEDURE diag_axis_add_attribute_i1d
94  END INTERFACE diag_axis_add_attribute
95 
96  !> @addtogroup diag_axis_mod
97  !> @{
98 
99 CONTAINS
100 
101  !> @brief Initialize the axis, and return the axis ID.
102  !!
103  !> <TT>diag_axis_init</TT> initializes an axis and returns the axis ID that
104  !! is to be used with <TT>register_diag_field</TT>. This function also
105  !! increments the axis counter and fills in the axes
106  !!
107  !! @return integer axis ID
108  INTEGER FUNCTION diag_axis_init(name, array_data, units, cart_name, long_name, direction,&
109  & set_name, edges, Domain, Domain2, DomainU, aux, req, tile_count, domain_position )
110  CHARACTER(len=*), INTENT(in) :: name !< Short name for axis
111  CLASS(*), DIMENSION(:), INTENT(in) :: array_data !< Array of coordinate values
112  CHARACTER(len=*), INTENT(in) :: units !< Units for the axis
113  CHARACTER(len=*), INTENT(in) :: cart_name !< Cartesian axis ("X", "Y", "Z", "T")
114  CHARACTER(len=*), INTENT(in), OPTIONAL :: long_name !< Long name for the axis.
115  CHARACTER(len=*), INTENT(in), OPTIONAL :: set_name
116  INTEGER, INTENT(in), OPTIONAL :: direction !< Indicates the direction of the axis
117  INTEGER, INTENT(in), OPTIONAL :: edges !< Axis ID for the previously defined "edges axis"
118  TYPE(domain1d), INTENT(in), OPTIONAL :: domain
119  TYPE(domain2d), INTENT(in), OPTIONAL :: domain2
120  TYPE(domainug), INTENT(in), OPTIONAL :: domainu
121  CHARACTER(len=*), INTENT(in), OPTIONAL :: aux !< Auxiliary name, can only be <TT>geolon_t</TT> or <TT>geolat_t</TT>
122  CHARACTER(len=*), INTENT(in), OPTIONAL :: req !< Required field names.
123  INTEGER, INTENT(in), OPTIONAL :: tile_count
124  INTEGER, INTENT(in), OPTIONAL :: domain_position
125 
126 
127  TYPE(domain1d) :: domain_x, domain_y
128  INTEGER :: ierr, axlen
129  INTEGER :: i, set, tile
130  INTEGER :: isc, iec, isg, ieg
131  CHARACTER(len=128) :: emsg
132 
133  IF ( .NOT.module_is_initialized ) THEN
134  CALL write_version_number("DIAG_AXIS_MOD", version)
135  ENDIF
136 
137  if (use_modern_diag) then
138  !TODO Passing in the axis_length because of a gnu issue where inside fms_diag_axis_init, the size of DATA
139  !was 2 which was causing the axis_data to not be written correctly...
140  diag_axis_init = fms_diag_object%fms_diag_axis_init(name, array_data, units, cart_name, size(array_data(:)), &
141  & long_name=long_name, direction=direction, set_name=set_name, edges=edges, domain=domain, domain2=domain2, &
142  & domainu=domainu, aux=aux, req=req, tile_count=tile_count, domain_position=domain_position)
143  return
144  endif
145  IF ( PRESENT(tile_count)) THEN
146  tile = tile_count
147  ELSE
148  tile = 1
149  END IF
150 
151  ! Allocate the axes
152  IF (.NOT. ALLOCATED(axis_sets)) ALLOCATE(axis_sets(max_num_axis_sets))
153  IF (.NOT. ALLOCATED(axes)) ALLOCATE(axes(max_axes))
154  IF (.NOT. ALLOCATED(num_subaxes)) THEN
155  ALLOCATE(num_subaxes(max_axes))
156  num_subaxes = 0
157  END IF
158 
159  !---- is there an axis set? ----
160  IF ( PRESENT(set_name) ) THEN
161  set = get_axis_set_num(set_name)
162  !---- add new set name ----
163  IF (set == 0) THEN
164  num_axis_sets = num_axis_sets + 1
165  IF ( num_axis_sets > max_num_axis_sets ) THEN
166  WRITE (emsg, fmt='("num_axis_sets (",I2,") exceeds max_num_axis_sets (",I2,"). ")')&
167  & num_axis_sets, max_num_axis_sets
168  ! <ERROR STATUS="FATAL">
169  ! num_axis_sets (<num_axis_sets>) exceeds max_num_axis_sets(<num_axis_sets>).
170  ! Increase max_num_axis_sets via diag_manager_nml.
171  ! </ERROR>
172  CALL error_mesg('diag_axis_mod::diag_axis_init',&
173  & trim(emsg)//' Increase max_num_axis_sets via diag_manager_nml.', fatal)
174  END IF
175  set = num_axis_sets
176  axis_sets(set) = set_name
177  END IF
178  ELSE
179  set = 0
180  END IF
181 
182  !---- see if axis already exists --
183  ! if this is time axis, return the ID of a previously defined
184  ! if this is spatial axis, FATAL error
185  DO i = 1, num_def_axes
186  IF ( trim(name) == axes(i)%name ) THEN
187  IF ( trim(name) == 'Stations' .OR. trim(name) == 'Levels') THEN
188  diag_axis_init = i
189  RETURN
190  ELSE IF ( set == axes(i)%set ) THEN
191  IF ( trim(lowercase(name)) == 'time' .OR.&
192  & trim(lowercase(cart_name)) == 't' .OR.&
193  & trim(lowercase(name)) == 'nv' .OR.&
194  & trim(lowercase(cart_name)) == 'n' ) THEN
195  diag_axis_init = i
196  RETURN
197  ELSE IF ( (lowercase(cart_name) /= 'x' .AND. lowercase(cart_name) /= 'y')&
198  & .OR. tile /= axes(i)%tile_count) THEN
199  ! <ERROR STATUS="FATAL">axis_name <NAME> and axis_set already exist.</ERROR>
200  CALL error_mesg('diag_axis_mod::diag_axis_init',&
201  & 'axis_name '//trim(name)//' and axis_set already exist.', fatal)
202  END IF
203  END IF
204  END IF
205  END DO
206 
207  !---- register axis ----
208  num_def_axes = num_def_axes + 1
209  ! <ERROR STATUS="FATAL">max_axes exceeded, increase it via diag_manager_nml</ERROR>
210  IF (num_def_axes > max_axes) CALL error_mesg ('diag_axis_mod::diag_axis_init',&
211  & 'max_axes exceeded, increase via diag_manager_nml', fatal)
212  diag_axis_init = num_def_axes
213 
214  !---- check and then save cart_name name ----
215  IF ( trim(uppercase(cart_name)) == 'X' .OR.&
216  & trim(uppercase(cart_name)) == 'Y' .OR.&
217  & trim(uppercase(cart_name)) == 'Z' .OR.&
218  & trim(uppercase(cart_name)) == 'T' .OR.&
219  & trim(uppercase(cart_name)) == 'U' .OR.&
220  & trim(uppercase(cart_name)) == 'N' ) THEN
221  axes(diag_axis_init)%cart_name = trim(uppercase(cart_name))
222  ELSE
223  ! <ERROR STATUS="FATAL">Invalid cart_name name.</ERROR>
224  CALL error_mesg('diag_axis_mod::diag_axis_init', 'Invalid cart_name name. '//trim(uppercase(cart_name)), fatal)
225  END IF
226 
227  !---- allocate storage for coordinate values of axis ----
228  IF ( axes(diag_axis_init)%cart_name == 'T' ) THEN
229  axlen = 0
230  ELSE
231  axlen = SIZE(array_data(:))
232  END IF
233  ALLOCATE ( axes(diag_axis_init)%diag_type_data(1:axlen) )
234 
235  ! Initialize Axes(diag_axis_init)
236  axes(diag_axis_init)%name = trim(name)
237  SELECT TYPE (array_data)
238  TYPE IS (real(kind=r4_kind))
239  axes(diag_axis_init)%diag_type_data = array_data(1:axlen)
240  TYPE IS (real(kind=r8_kind))
241  axes(diag_axis_init)%diag_type_data = real(array_data(1:axlen))
242  CLASS DEFAULT
243  CALL error_mesg('diag_axis_mod::diag_axis_init',&
244  & 'The axis data is not one of the supported types of real(kind=4) or real(kind=8)', fatal)
245  END SELECT
246  axes(diag_axis_init)%units = units
247  axes(diag_axis_init)%length = axlen
248  axes(diag_axis_init)%set = set
249  ! start and end are used in subaxes information only
250  axes(diag_axis_init)%start = -1
251  axes(diag_axis_init)%end = -1
252  axes(diag_axis_init)%subaxis_name = ""
253  axes(diag_axis_init)%shift = 0
254  axes(diag_axis_init)%num_attributes = 0
255 
256  IF ( PRESENT(long_name) ) THEN
257  axes(diag_axis_init)%long_name = long_name
258  ELSE
259  axes(diag_axis_init)%long_name = name
260  END IF
261 
262  IF ( PRESENT(aux) ) THEN
263  axes(diag_axis_init)%aux = trim(aux)
264  ELSE
265  axes(diag_axis_init)%aux = 'none'
266  END IF
267 
268  IF ( PRESENT(req) ) THEN
269  axes(diag_axis_init)%req = trim(req)
270  ELSE
271  axes(diag_axis_init)%req = 'none'
272  END IF
273  IF ( PRESENT(domain_position) ) THEN
274  if (domain_position == north .or. domain_position == east .or. domain_position == center) then
275  axes(diag_axis_init)%domain_position = domain_position
276  else
277  CALL error_mesg('diag_axis_mod::diag_axis_init', "Position must be NORTH, EAST, or CENTER" ,&
278  fatal)
279  endif
280  ELSE
281  axes(diag_axis_init)%domain_position = center
282  END IF
283 
284  !---- axis direction (-1, 0, or +1) ----
285  IF ( PRESENT(direction) )THEN
286  IF ( abs(direction) /= 1 .AND. direction /= 0 )&
287  ! <ERROR STATUS="FATAL">direction must be 0, +1, or -1</ERROR>
288  & CALL error_mesg('diag_axis_mod::diag_axis_init', 'direction must be 0, +1 or -1', fatal)
289  axes(diag_axis_init)%direction = direction
290  ELSE
291  axes(diag_axis_init)%direction = 0
292  END IF
293 
294  !---- Handle the DomainU check
295  IF (present(domainu) .AND. (PRESENT(domain2) .OR. PRESENT(domain)) ) THEN
296  ! <ERROR STATUS="FATAL">Presence of DomainU and another Domain at the same time is prohibited</ERROR>
297  CALL error_mesg('diag_axis_mod::diag_axis_init',&
298  & 'Presence of DomainU and another Domain at the same time is prohibited', fatal)
299  !---- domain2d type ----
300  ELSE IF ( PRESENT(domain2) .AND. PRESENT(domain)) THEN
301  ! <ERROR STATUS="FATAL">Presence of both Domain and Domain2 at the same time is prohibited</ERROR>
302  CALL error_mesg('diag_axis_mod::diag_axis_init',&
303  & 'Presence of both Domain and Domain2 at the same time is prohibited', fatal)
304  ELSE IF ( PRESENT(domain2) .OR. PRESENT(domain)) THEN
305  IF ( axes(diag_axis_init)%cart_name /= 'X' .AND. axes(diag_axis_init)%cart_name /= 'Y') THEN
306  ! <ERROR STATUS="FATAL">Domain must not be present for an axis which is not in the X or Y direction.</ERROR>
307  CALL error_mesg('diag_axis_mod::diag_axis_init',&
308  & 'A Structured Domain must not be present for an axis which is not in the X or Y direction', fatal)
309  END IF
310  ELSE IF (present(domainu) .AND. axes(diag_axis_init)%cart_name /= 'U') THEN
311  CALL error_mesg('diag_axis_mod::diag_axis_init',&
312  & 'In the unstructured domain, the axis cart_name must be U', fatal)
313  END IF
314 
315  axes(diag_axis_init)%tile_count = tile
316 
317  IF ( PRESENT(domain2) ) THEN
318  axes(diag_axis_init)%Domain2 = domain2
319  CALL mpp_get_domain_components(domain2, domain_x, domain_y, tile_count=tile_count)
320  IF ( axes(diag_axis_init)%cart_name == 'X' ) axes(diag_axis_init)%Domain = domain_x
321  IF ( axes(diag_axis_init)%cart_name == 'Y' ) axes(diag_axis_init)%Domain = domain_y
322  axes(diag_axis_init)%DomainUG = null_domainug
323  ELSE IF ( PRESENT(domain)) THEN
324  !---- domain1d type ----
325  axes(diag_axis_init)%Domain2 = null_domain2d ! needed since not 2-D domain
326  axes(diag_axis_init)%Domain = domain
327  axes(diag_axis_init)%DomainUG = null_domainug
328  ELSE IF (present(domainu)) THEN
329  axes(diag_axis_init)%Domain2 = null_domain2d
330  axes(diag_axis_init)%Domain = null_domain1d
331  axes(diag_axis_init)%DomainUG = domainu
332  ELSE
333  axes(diag_axis_init)%Domain2 = null_domain2d
334  axes(diag_axis_init)%Domain = null_domain1d
335  axes(diag_axis_init)%DomainUG = null_domainug
336  END IF
337 
338  !--- set up the shift value for x-y axis
339  IF ( axes(diag_axis_init)%Domain .NE. null_domain1d ) THEN
340  CALL mpp_get_compute_domain(axes(diag_axis_init)%Domain, isc, iec)
341  CALL mpp_get_global_domain(axes(diag_axis_init)%Domain, isg, ieg)
342  IF ( axes(diag_axis_init)%length == ieg - isg + 2 ) THEN
343  axes(diag_axis_init)%shift = 1
344  END IF
345  END IF
346 
347  !---- have axis edges been defined ? ----
348  axes(diag_axis_init)%edges = 0
349  IF (PRESENT(edges) ) THEN
350  IF ( edges > 0 .AND. edges < num_def_axes ) THEN
351  ierr=0
352  IF ( axes(edges)%cart_name /= axes(diag_axis_init)%cart_name) ierr=1
353  IF ( axes(edges)%length /= axes(diag_axis_init)%length+1 ) ierr=ierr+2
354  IF ( axes(edges)%set /= axes(diag_axis_init)%set ) ierr=ierr+4
355  IF ( ierr > 0 ) THEN
356  ! <ERROR STATUS="FATAL">Edges axis does not match axis (code <CODE>).</ERROR>
357  WRITE (emsg,'("Edges axis does not match axis (code ",I1,").")') ierr
358  CALL error_mesg('diag_axis_mod::diag_axis_init', emsg, fatal)
359  END IF
360  axes(diag_axis_init)%edges = edges
361  ELSE
362  ! <ERROR STATUS="FATAL">Edges axis is not defined.</ERROR>
363  CALL error_mesg('diag_axis_mod::diag_axis_init', 'Edges axis is not defined', fatal)
364  END IF
365  END IF
366 
367  ! Module is now initialized
368  module_is_initialized = .true.
369 
370  END FUNCTION diag_axis_init
371 
372  !> @brief Create a subaxis on a parent axis.
373  !!
374  !> Given the ID of a parent axis, create a subaxis and fill it with data,
375  !! and return the ID of the corresponding subaxis.
376  !!
377  !! The subaxis is defined on the parent axis from <TT>start_indx</TT>
378  !! to <TT>end_indx</TT>.
379  !!
380  !! @return Integer ID of the corresponding subaxis.
381  INTEGER FUNCTION diag_subaxes_init(axis, subdata, start_indx, end_indx, domain_2d)
382  INTEGER, INTENT(in) :: axis !< ID of the parent axis
383  REAL, DIMENSION(:), INTENT(in) :: subdata !< Data of the subaxis
384  INTEGER, INTENT(in) :: start_indx !< Start index of the subaxis
385  INTEGER, INTENT(in) :: end_indx !< End index of the subaxis
386  TYPE(domain2d), INTENT(in), OPTIONAL :: domain_2d
387 
388  INTEGER :: i, nsub_axis, direction
389  INTEGER :: xbegin, xend, ybegin, yend
390  INTEGER :: ad_xbegin, ad_xend, ad_ybegin, ad_yend
391  CHARACTER(len=128) :: name, nsub_name
392  CHARACTER(len=128) :: units
393  CHARACTER(len=128) :: cart_name
394  CHARACTER(len=128) :: long_name
395  CHARACTER(len=128) :: emsg
396  LOGICAL :: subaxis_set, hasdomain
397 
398  ! there may be more than 1 subaxis on a parent axis, check for redundancy
399  nsub_axis = 0
400  subaxis_set = .false.
401 
402  IF ( PRESENT(domain_2d) ) THEN
403  hasdomain = .true.
404  CALL mpp_get_compute_domain(domain_2d, xbegin, xend, ybegin, yend)
405  ELSE
406  hasdomain = .false.
407  END IF
408  sa_search: DO i = 1, num_subaxes(axis)
409  IF ( start_indx == axes(axis)%start(i) .AND. end_indx == axes(axis)%end(i) ) THEN
410  IF ( hasdomain ) THEN
411  CALL mpp_get_compute_domain(axes(axis)%subaxis_domain2(i), ad_xbegin, ad_xend, ad_ybegin, ad_yend)
412  IF ( .NOT.((xbegin == ad_xbegin .AND. xend == ad_xend) .AND.&
413  & (ybegin == ad_ybegin .AND. yend == ad_yend)) ) THEN
414  cycle sa_search
415  END IF
416  END IF
417  nsub_axis = i
418  subaxis_set = .true. !subaxis already exists
419  name = trim(axes(axis)%subaxis_name(nsub_axis))
420  EXIT sa_search
421  END IF
422  END DO sa_search
423 
424  IF ( nsub_axis == 0 ) THEN ! create new subaxis
425  num_subaxes(axis) = num_subaxes(axis) + 1
426  IF (num_subaxes(axis) > max_subaxes) THEN
427  ! <ERROR STATUS="FATAL">max_subaxes (value <VALUE>) is too small. Consider increasing max_subaxes.</ERROR>
428  WRITE (emsg,'("max_subaxes (value ",I4,") is too small. Consider increasing max_subaxes.")') max_subaxes
429  CALL error_mesg('diag_axis_mod::diag_subaxes_init', emsg, fatal)
430  END IF
431  nsub_axis = num_subaxes(axis)
432  axes(axis)%start(nsub_axis) = start_indx
433  axes(axis)%end(nsub_axis) = end_indx
434  if ( hasdomain ) axes(axis)%subaxis_domain2(nsub_axis) = domain_2d
435  END IF
436 
437  ! Create new name for the subaxis from name of parent axis
438  ! If subaxis already exists, get the index and return
439  IF(subaxis_set) THEN
440  IF ( axes(axis)%set > 0 ) THEN
441  diag_subaxes_init = get_axis_num(name, set_name=trim(axis_sets(axes(axis)%set)))
442  ELSE
444  END IF
445  ELSE
446  ! get a new index for subaxis
447  !::sdu:: Need a check to allow larger numbers in the index number.
448  WRITE (nsub_name,'(I2.2)') nsub_axis
449  name = trim(axes(axis)%name)//'_sub'//trim(nsub_name)
450  axes(axis)%subaxis_name(nsub_axis) = name
451  long_name = trim(axes(axis)%long_name)
452  units = trim(axes(axis)%units)
453  cart_name = trim(axes(axis)%cart_name)
454  direction = axes(axis)%direction
455  IF (axes(axis)%set > 0) THEN
456  diag_subaxes_init = diag_axis_init(trim(name), subdata, trim(units), trim(cart_name), trim(long_name),&
457  & set_name=trim(axis_sets(axes(axis)%set)), direction=direction, domain2=domain_2d)
458  ELSE
459  diag_subaxes_init = diag_axis_init(trim(name), subdata, trim(units), trim(cart_name), trim(long_name),&
460  & direction=direction, domain2=domain_2d)
461  END IF
462  END IF
463  END FUNCTION diag_subaxes_init
464  !> @brief Return information about the axis with index ID
465  SUBROUTINE get_diag_axis(id, name, units, long_name, cart_name,&
466  & direction, edges, Domain, DomainU, array_data, num_attributes, attributes, domain_position)
467  CHARACTER(len=*), INTENT(out) :: name, units, long_name, cart_name
468  INTEGER, INTENT(in) :: id !< Axis ID
469  TYPE(domain1d), INTENT(out) :: domain
470  TYPE(domainug), INTENT(out) :: domainu
471  INTEGER, INTENT(out) :: direction !< Direction of data. (See <TT>@ref diag_axis_init</TT> for a description of
472  !! allowed values)
473  INTEGER, INTENT(out) :: edges !< Axis ID for the previously defined "edges axis".
474  CLASS(*), DIMENSION(:), INTENT(out) :: array_data !< Array of coordinate values for this axis.
475  INTEGER, INTENT(out), OPTIONAL :: num_attributes
476  TYPE(diag_atttype), ALLOCATABLE, DIMENSION(:), INTENT(out), OPTIONAL :: attributes
477  INTEGER, INTENT(out), OPTIONAL :: domain_position
478 
479  INTEGER :: i, j, istat
480 
481  CALL valid_id_check(id, 'get_diag_axis')
482  name = axes(id)%name
483  units = axes(id)%units
484  long_name = axes(id)%long_name
485  cart_name = axes(id)%cart_name
486  direction = axes(id)%direction
487  edges = axes(id)%edges
488  domain = axes(id)%Domain
489  domainu = axes(id)%DomainUG
490  if (present(domain_position)) domain_position = axes(id)%domain_position
491  IF ( axes(id)%length > SIZE(array_data(:)) ) THEN
492  ! <ERROR STATUS="FATAL">array data is too small.</ERROR>
493  CALL error_mesg('diag_axis_mod::get_diag_axis', 'array data is too small', fatal)
494  ELSE
495  SELECT TYPE (array_data)
496  TYPE IS (real(kind=r4_kind))
497  array_data(1:axes(id)%length) = real(axes(id)%diag_type_data(1:axes(id)%length), kind=r4_kind)
498  TYPE IS (real(kind=r8_kind))
499  array_data(1:axes(id)%length) = axes(id)%diag_type_data(1:axes(id)%length)
500  CLASS DEFAULT
501  CALL error_mesg('diag_axis_mod::get_diag_axis',&
502  & 'The axis data is not one of the supported types of real(kind=4) or real(kind=8)', fatal)
503  END SELECT
504  END IF
505  IF ( PRESENT(num_attributes) ) THEN
506  num_attributes = axes(id)%num_attributes
507  END IF
508  IF ( PRESENT(attributes) ) THEN
509  IF ( allocated(axes(id)%attributes) ) THEN
510  IF ( ALLOCATED(attributes) ) THEN
511  ! If allocate, make sure attributes is large enough to hold Axis(id)%attributes
512  IF ( axes(id)%num_attributes .GT. SIZE(attributes(:)) ) THEN
513  CALL error_mesg('diag_axis_mod::get_diag_axis', 'array attribute is too small', fatal)
514  END IF
515  ELSE
516  ! Allocate attributes
517  ALLOCATE(attributes(axes(id)%num_attributes), stat=istat)
518  IF ( istat .NE. 0 ) THEN
519  CALL error_mesg('diag_axis_mod::get_diag_axis', 'Unable to allocate memory for attribute', fatal)
520  END IF
521  END IF
522  DO i=1, axes(id)%num_attributes
523  ! Unallocate all att arrays in preparation for new data
524  IF ( allocated(attributes(i)%fatt) ) THEN
525  DEALLOCATE(attributes(i)%fatt)
526  END IF
527  IF ( allocated(attributes(i)%iatt) ) THEN
528  DEALLOCATE(attributes(i)%iatt)
529  END IF
530 
531  ! Copy in attribute data
532  attributes(i)%type = axes(id)%attributes(i)%type
533  attributes(i)%len = axes(id)%attributes(i)%len
534  attributes(i)%name = axes(id)%attributes(i)%name
535  attributes(i)%catt = axes(id)%attributes(i)%catt
536  ! Allocate fatt arrays (if needed), and copy in data
537  IF ( allocated(axes(id)%attributes(i)%fatt) ) THEN
538  ALLOCATE(attributes(i)%fatt(SIZE(axes(id)%attributes(i)%fatt(:))), stat=istat)
539  IF ( istat .NE. 0 ) THEN
540  CALL error_mesg('diag_axis_mod::get_diag_axis', &
541  & 'Unable to allocate memory for attribute%fatt', fatal)
542  END IF
543  DO j=1, SIZE(attributes(i)%fatt(:))
544  attributes(i)%fatt(j) = axes(id)%attributes(i)%fatt(j)
545  END DO
546  END IF
547  ! Allocate iatt arrays (if needed), and copy in data
548  IF ( allocated(axes(id)%attributes(i)%iatt) ) THEN
549  ALLOCATE(attributes(i)%iatt(SIZE(axes(id)%attributes(i)%iatt(:))), stat=istat)
550  IF ( istat .NE. 0 ) THEN
551  CALL error_mesg('diag_axis_mod::get_diag_axis', &
552  & 'Unable to allocate memory for attribute%iatt', fatal)
553  END IF
554  DO j=1, SIZE(attributes(i)%iatt(:))
555  attributes(i)%iatt(j) = axes(id)%attributes(i)%iatt(j)
556  END DO
557  END IF
558  END DO
559  END IF
560  END IF
561  END SUBROUTINE get_diag_axis
562 
563  !> @brief Return the axis cartesian.
564  SUBROUTINE get_diag_axis_cart(id, cart_name)
565  INTEGER, INTENT(in) :: id !< Axis ID
566  CHARACTER(len=*), INTENT(out) :: cart_name !< Cartesian axis
567 
568  CALL valid_id_check(id, 'get_diag_axis_cart')
569  cart_name = axes(id)%cart_name
570  END SUBROUTINE get_diag_axis_cart
571 
572  !> @brief Return the axis data.
573  SUBROUTINE get_diag_axis_data(id, axis_data)
574  INTEGER, INTENT(in) :: id !< Axis ID
575  REAL, DIMENSION(:), INTENT(out) :: axis_data !< Axis data
576 
577  CALL valid_id_check(id, 'get_diag_axis_data')
578  IF (axes(id)%length > SIZE(axis_data(:))) THEN
579  ! <ERROR STATUS="FATAL">array data is too small</ERROR>
580  CALL error_mesg('diag_axis_mod::get_diag_axis_data', 'array data is too small', fatal)
581  ELSE
582  axis_data(1:axes(id)%length) = axes(id)%diag_type_data
583  END IF
584  END SUBROUTINE get_diag_axis_data
585 
586  !> @brief Return the short name of the axis.
587  SUBROUTINE get_diag_axis_name(id, axis_name)
588  INTEGER , INTENT(in) :: id !< Axis ID
589  CHARACTER(len=*), INTENT(out) :: axis_name !< Axis short name
590 
591  if (use_modern_diag) then
592  axis_name = fms_diag_object%fms_get_axis_name_from_id(id)
593  else
594  CALL valid_id_check(id, 'get_diag_axis_name')
595  axis_name = axes(id)%name
596  endif
597  END SUBROUTINE get_diag_axis_name
598 
599  !> @brief Return the name of the axis' domain
600  SUBROUTINE get_diag_axis_domain_name(id, name)
601  INTEGER, INTENT(in) :: id !< Axis ID
602  CHARACTER(len=*), INTENT(out) :: name !< Axis' domain name
603 
604  CALL valid_id_check(id, 'get_diag_axis_domain_name')
605  name = mpp_get_domain_name(axes(id)%domain2)
606  END SUBROUTINE get_diag_axis_domain_name
607 
608  !> @brief Return the length of the axis.
609  !> @return length of axis as an integer
610  INTEGER FUNCTION get_axis_length(id)
611  INTEGER, INTENT(in) :: id !< Axis ID
612  INTEGER :: length
613 
614  if (use_modern_diag) then
615  get_axis_length = fms_diag_object%fms_get_axis_length(id)
616  else
617  CALL valid_id_check(id, 'get_axis_length')
618  IF ( axes(id)%Domain .NE. null_domain1d ) THEN
619  CALL mpp_get_compute_domain(axes(id)%Domain,size=length)
620  !---one extra point is needed for some case. ( like symmetry domain )
621  get_axis_length = length + axes(id)%shift
622  ELSE
623  get_axis_length = axes(id)%length
624  END IF
625  endif
626  END FUNCTION get_axis_length
627 
628  !> @brief Return the auxiliary name for the axis.
629  !! @return auxiliary name for the axis
630  CHARACTER(len=128) FUNCTION get_axis_aux(id)
631  INTEGER, INTENT(in) :: id !< Axis ID
632 
633  CALL valid_id_check(id, 'get_axis_aux')
634  get_axis_aux = axes(id)%aux
635  END FUNCTION get_axis_aux
636 
637  !> @brief Return the required field names for the axis.
638  !! @return required field names for the axis
639  CHARACTER(len=128) FUNCTION get_axis_reqfld(id)
640  INTEGER, INTENT(in) :: id !< Axis ID
641 
642  CALL valid_id_check(id, 'get_axis_reqfld')
643  get_axis_reqfld = axes(id)%req
644  END FUNCTION get_axis_reqfld
645 
646  !> @brief Return the global length of the axis.
647  !! @return global length of the axis
648  INTEGER FUNCTION get_axis_global_length(id)
649  INTEGER, INTENT(in) :: id !< Axis ID
650 
651  CALL valid_id_check(id, 'get_axis_global_length')
652  get_axis_global_length = axes(id)%length
653  END FUNCTION get_axis_global_length
654 
655  !> @brief Return the tile count for the axis.
656  !! @return tile count for the axis
657  INTEGER FUNCTION get_tile_count(ids)
658  INTEGER, DIMENSION(:), INTENT(in) :: ids !< Axis IDs.
659  !! Possible dimensions: 1 <= <TT>size(ids(:))</TT> <= 4.
660 
661  INTEGER :: i, id, flag
662 
663  IF ( SIZE(ids(:)) < 1 ) THEN
664  ! <ERROR STATUS="FATAL">input argument has incorrect size.</ERROR>
665  CALL error_mesg('diag_axis_mod::get_tile_count', 'input argument has incorrect size', fatal)
666  END IF
667  get_tile_count = 1
668  flag = 0
669  DO i = 1, SIZE(ids(:))
670  id = ids(i)
671  CALL valid_id_check(id, 'get_tile_count')
672  IF ( axes(id)%cart_name == 'X' .OR. &
673  axes(id)%cart_name == 'Y' ) flag = flag + 1
674  ! --- both x/y axes found ---
675  IF ( flag == 2 ) THEN
676  get_tile_count = axes(id)%tile_count
677  EXIT
678  END IF
679  END DO
680  END FUNCTION get_tile_count
681 
682  !> @brief Retrun the 1D domain for the axis ID given.
683  !! @return 1D domain for the axis ID given
684  TYPE(domain1d) function get_domain1d(id)
685  INTEGER, INTENT(in) :: id !< Axis ID
686 
687  CALL valid_id_check(id, 'get_domain1d')
688  IF (axes(id)%Domain .NE. null_domain1d) THEN
689  get_domain1d = axes(id)%Domain
690  ELSE
691  get_domain1d = null_domain1d
692  ENDIF
693  END FUNCTION get_domain1d
694 
695  !> @brief Return the 2D domain for the axis IDs given.
696  !! @return 2D domain for the axis IDs given
697  TYPE(domain2d) function get_domain2d(ids)
698  INTEGER, DIMENSION(:), INTENT(in) :: ids !< Axis IDs.
699  !! Possible dimensions: 1 <= <TT>size(ids(:))</TT> <= 4.
700 
701  INTEGER :: i, id, flag
702 
703  IF ( SIZE(ids(:)) < 1 ) THEN
704  ! <ERROR STATUS="FATAL">input argument has incorrect size.</ERROR>
705  CALL error_mesg('diag_axis_mod::get_domain2d', 'input argument has incorrect size', fatal)
706  END IF
707 
708  if (use_modern_diag) then
709  get_domain2d = fms_diag_object%fms_get_domain2d(ids)
710  return
711  endif
712 
713  get_domain2d = null_domain2d
714  flag = 0
715  DO i = 1, SIZE(ids(:))
716  id = ids(i)
717  CALL valid_id_check(id, 'get_domain2d')
718  IF ( axes(id)%cart_name == 'X' .OR. axes(id)%cart_name == 'Y' ) flag = flag + 1
719  ! --- both x/y axes found ---
720  IF ( flag == 2 ) THEN
721  IF (axes(id)%Domain2 .NE. null_domain2d) get_domain2d = axes(id)%Domain2
722  EXIT
723  END IF
724  END DO
725  END FUNCTION get_domain2d
726 
727  !> @brief Retrun the 1D domain for the axis ID given.
728  !! @return 1D domain for the axis ID given
729  TYPE(domainug) function get_domainug(id)
730  INTEGER, INTENT(in) :: id !< Axis ID
731 
732  CALL valid_id_check(id, 'get_domainUG')
733  IF (axes(id)%DomainUG .NE. null_domainug) THEN
734  get_domainug = axes(id)%DomainUG
735  ELSE
736  get_domainug = null_domainug
737  ENDIF
738  END FUNCTION get_domainug
739 
740 !ug support
741  !> @brief Checks if the axes are compatible
742  !! @return integer domain_type
743  function axis_compatible_check(id,varname) result(domain_type)
744 
745  !Inputs/Outputs
746  integer,dimension(:),intent(in) :: id !<The array of axis IDs
747  character(*),intent(in),optional :: varname !<The name of the variable
748  integer(I4_KIND) :: domain_type !<DIAG_AXIS_NODOMAIN = no domain.
749  !<DIAG_AXIS_2DDOMAIN = structured domain.
750  !<DIAG_AXIS_UGDOMAIN = unstructured domain.
751 
752  !Local variables
753  logical :: xory !<XorY set to true if X or Y is found as a cart_name.
754  logical :: ug !<UG set to true if U is found as a cart_name.
755  integer :: n !<Looping index.
756  logical :: uses_domain2d !<True if an axis is associated with a 2D domain.
757  logical :: uses_domainug !<True if an axis is associated with an unstructured domain.
758 
759  !Initialize flags.
760  xory = .false.
761  ug = .false.
762  uses_domain2d = .false.
763  uses_domainug = .false.
764 
765  !Make sure that valid set of axes was passed, and determine the domain-type
766  !associated with the axes.
767  do n = 1,size(id)
768  call valid_id_check(id(n), &
769  "axis_compatible_check")
770  if (axes(id(n))%cart_name .eq. "X" .or. &
771  axes(id(n))%cart_name .eq. "Y") then
772  xory = .true.
773  elseif (axes(id(n))%cart_name .eq. "U") then
774  ug = .true.
775  endif
776  if (axes(id(n))%Domain2 .ne. null_domain2d) then
777  uses_domain2d = .true.
778  elseif (axes(id(n))%DomainUG .ne. null_domainug) then
779  uses_domainug = .true.
780  endif
781  enddo
782  if (ug .and. xory) then
783  if (present(varname)) then
784  call error_mesg("axis_compatible_check", &
785  "Can not use an unstructured grid with a "// &
786  "horizontal cartesian coordinate for the field " &
787  //trim(varname), &
788  fatal)
789  else
790  call error_mesg("axis_compatible_check", &
791  "Can not use an unstructured grid with a horizontal "// &
792  "cartesian coordinate", &
793  fatal)
794  endif
795  endif
796  if (uses_domain2d .and. uses_domainug) then
797  if (present(varname)) then
798  call error_mesg("axis_compatible_check", &
799  "Can not use an unstructured grid with a"// &
800  "structured grid for the field "//trim(varname), &
801  fatal)
802  else
803  call error_mesg("axis_compatible_check", &
804  "Can not use an unstructured grid with a"// &
805  "structured grid.", &
806  fatal)
807  endif
808  endif
809  if (uses_domain2d) then
810  domain_type = diag_axis_2ddomain
811  elseif (uses_domainug) then
812  domain_type = diag_axis_ugdomain
813  else
814  domain_type = diag_axis_nodomain
815  endif
816 
817  return
818  end function axis_compatible_check
819 
820  !> @brief Return the value of the shift for the axis IDs given.
821  SUBROUTINE get_axes_shift(ids, ishift, jshift)
822  INTEGER, DIMENSION(:), INTENT(in) :: ids
823  INTEGER, INTENT(out) :: ishift !< X shift value.
824  INTEGER, INTENT(out) :: jshift !< Y shift value.
825 
826  INTEGER :: i, id
827 
828  !-- get the value of the shift.
829  ishift = 0
830  jshift = 0
831  DO i = 1, SIZE(ids(:))
832  id = ids(i)
833  CALL valid_id_check(id, 'get_axes_shift')
834  SELECT CASE (axes(id)%cart_name)
835  CASE ( 'X' )
836  ishift = axes(id)%shift
837  CASE ( 'Y' )
838  jshift = axes(id)%shift
839  END SELECT
840  END DO
841  END SUBROUTINE get_axes_shift
842 
843  !> @brief Returns index into axis table corresponding to a given axis name.
844  !! @return Returns index into axis table corresponding to a given axis name.
845  INTEGER FUNCTION get_axis_num(axis_name, set_name)
846  CHARACTER(len=*), INTENT(in) :: axis_name !< Axis name
847  CHARACTER(len=*), INTENT(in), OPTIONAL :: set_name !< Set name
848 
849  INTEGER :: set, n
850 
851  IF ( PRESENT(set_name) ) THEN
852  set = get_axis_set_num(trim(set_name))
853  ELSE
854  set = 0
855  END IF
856  get_axis_num = 0
857  DO n = 1, num_def_axes
858  IF ( trim(axis_name) == trim(axes(n)%name) .AND. axes(n)%set == set ) THEN
859  get_axis_num = n
860  RETURN
861  END IF
862  END DO
863  END FUNCTION get_axis_num
864 
865  !> @brief Returns index in axis set table corresponding to a given axis set name
866  !! @return Returns index in axis set table corresponding to a given axis set name
867  INTEGER FUNCTION get_axis_set_num (set_name)
868  CHARACTER(len=*), INTENT(in) :: set_name !< Set name
869 
870  INTEGER :: iset
871 
872  get_axis_set_num = 0
873  DO iset = 1, num_axis_sets
874  IF ( set_name == axis_sets(iset) ) THEN
875  get_axis_set_num = iset
876  RETURN
877  END IF
878  END DO
879  END FUNCTION get_axis_set_num
880 
881  !> @brief Check to see if the given axis id is a valid id. If the axis id is invalid,
882  !! call a FATAL error. If the ID is valid, just return.
883  SUBROUTINE valid_id_check(id, routine_name)
884  INTEGER, INTENT(in) :: id !< Axis is to check for validity
885  CHARACTER(len=*), INTENT(in) :: routine_name !< Name of the subroutine checking for a valid axis id
886 
887  CHARACTER(len=5) :: emsg
888 
889  IF ( id < 1 .OR. id > num_def_axes) THEN
890  ! <ERROR STATUS="FATAL">
891  ! Illegal value for axis used (value <VALUE>).
892  ! </ERROR>
893  WRITE (emsg, '(I2)') id
894  CALL error_mesg('diag_axis_mod::'//trim(routine_name),&
895  & 'Illegal value for axis_id used (value '//trim(emsg)//').', fatal)
896  END IF
897  END SUBROUTINE valid_id_check
898 
899  SUBROUTINE diag_axis_attribute_init(diag_axis_id, name, type, cval, ival, rval)
900  INTEGER, INTENT(in) :: diag_axis_id !< input field ID, obtained from diag_axis_mod::diag_axis_init.
901  CHARACTER(len=*) :: name !< Name of the attribute
902  INTEGER, INTENT(in) :: type !< NetCDF type (NF_FLOAT, NF_INT, NF_CHAR)
903  CHARACTER(len=*), INTENT(in), OPTIONAL :: cval !< Character string attribute value
904  INTEGER, DIMENSION(:), INTENT(in), OPTIONAL :: ival !< Integer attribute value(s)
905  REAL, DIMENSION(:), INTENT(in), OPTIONAL :: rval !< Real attribute value(s)
906 
907  INTEGER :: istat, length, i, this_attribute
908  CHARACTER(len=1024) :: err_msg
909 
910  IF ( .NOT.first_send_data_call ) THEN
911  ! Call error due to unable to add attribute after send_data called
912  ! <ERROR STATUS="FATAL">
913  ! Attempting to add attribute <name> to axis <axis_name>
914  ! after first send_data call. Too late.
915  ! </ERROR>
916  CALL error_mesg('diag_manager_mod::diag_axis_add_attribute', 'Attempting to add attribute "'&
917  &//trim(name)//'" to axis after first send_data call. Too late.', fatal)
918  END IF
919 
920  ! Simply return if diag_axis_id <= 0 --- not registered
921  IF ( diag_axis_id .LE. 0 ) THEN
922  RETURN
923  ELSE IF ( diag_axis_id .GT. num_def_axes ) THEN
924  ! Call error axis_id greater than num_def_axes. Axis is unknown
925  ! <ERROR STATUS="FATAL">
926  ! Attempting to add attribute <name> to axis ID <axis_ID>, however ID unknown.
927  ! </ERROR>
928  WRITE(err_msg, '(I5)') diag_axis_id
929  CALL error_mesg('diag_manager_mod::diag_axis_add_attribute', 'Attempting to add attribute "'&
930  &//trim(name)//'" to axis ID "'//trim(err_msg)//'", however ID unknown.', fatal)
931 
932  ELSE
933  ! Allocate memory for the attributes
934  CALL attribute_init_axis(axes(diag_axis_id))
935 
936  ! Check if attribute already exists
937  this_attribute = 0
938  DO i=1, axes(diag_axis_id)%num_attributes
939  IF ( trim(axes(diag_axis_id)%attributes(i)%name) .EQ. trim(name) ) THEN
940  this_attribute = i
941  EXIT
942  END IF
943  END DO
944 
945  IF ( this_attribute.NE.0 .AND. (type.EQ.nf90_int .OR. type.EQ.nf90_float) ) THEN
946  ! <ERROR STATUS="FATAL">
947  ! Attribute <name> already defined for axis <axis_name>.
948  ! Contact the developers
949  ! </ERROR>
950  CALL error_mesg('diag_manager_mod::diag_axis_add_attribute',&
951  & 'Attribute "'//trim(name)//'" already defined for axis "'&
952  &//trim(axes(diag_axis_id)%name)//'". Contact the developers.', fatal)
953  ELSE IF ( this_attribute.NE.0 .AND. type.EQ.nf90_char .AND. debug_diag_manager ) THEN
954  ! <ERROR STATUS="NOTE">
955  ! Attribute <name> already defined for axis <axis_name>.
956  ! Prepending.
957  ! </ERROR>
958  CALL error_mesg('diag_manager_mod::diag_axis_add_attribute',&
959  & 'Attribute "'//trim(name)//'" already defined for axis"'&
960  &//trim(axes(diag_axis_id)%name)//'". Prepending.', note)
961  ELSE
962  ! Defining a new attribute
963  ! Increase the number of field attributes
964  this_attribute = axes(diag_axis_id)%num_attributes + 1
965  ! Checking to see if num_attributes == max_axis_attributes, and return error message
966  IF ( this_attribute .GT. max_axis_attributes ) THEN
967  ! <ERROR STATUS="FATAL">
968  ! Number of attributes exceeds max_axis_attributes for attribute <name> for axis <axis_name>.
969  ! Increase diag_manager_nml:max_axis_attributes.
970  ! </ERROR>
971  CALL error_mesg('diag_manager_mod::diag_axis_add_attribute',&
972  & 'Number of attributes exceeds max_axis_attributes for attribute "'&
973  & //trim(name)//'" for axis "'//trim(axes(diag_axis_id)%name)&
974  & //'". Increase diag_manager_nml:max_axis_attributes.',&
975  & fatal)
976  ELSE
977  axes(diag_axis_id)%num_attributes = this_attribute
978  ! Set name and type
979  axes(diag_axis_id)%attributes(this_attribute)%name = name
980  axes(diag_axis_id)%attributes(this_attribute)%type = type
981  ! Initialize catt to a blank string, as len_trim doesn't always work on an uninitialized string
982  axes(diag_axis_id)%attributes(this_attribute)%catt = ''
983  END IF
984  END IF
985 
986  SELECT CASE (type)
987  CASE (nf90_int)
988  IF ( .NOT.PRESENT(ival) ) THEN
989  ! <ERROR STATUS="FATAL">
990  ! Number type claims INTEGER, but ival not present for attribute <name> for axis <axis_name>.
991  ! Contact the developers.
992  ! </ERROR>
993  CALL error_mesg('diag_manager_mod::diag_axis_add_attribute',&
994  & 'Attribute type claims INTEGER, but ival not present for attribute "'&
995  & //trim(name)//'" for axis "'//trim(axes(diag_axis_id)%name)&
996  & //'". Contact the developers.', fatal)
997  END IF
998  length = SIZE(ival)
999  ! Allocate iatt(:) to size of ival
1000  ALLOCATE(axes(diag_axis_id)%attributes(this_attribute)%iatt(length), stat=istat)
1001  IF ( istat.NE.0 ) THEN
1002  ! <ERROR STATUS="FATAL">
1003  ! Unable to allocate iatt for attribute <name> for axis <axis_name>
1004  ! </ERROR>
1005  CALL error_mesg('diag_manager_mod::diag_axis_add_attribute', 'Unable to allocate iatt for attribute "'&
1006  & //trim(name)//'" for axis "'//trim(axes(diag_axis_id)%name)//'"', fatal)
1007  END IF
1008  ! Set remaining fields
1009  axes(diag_axis_id)%attributes(this_attribute)%len = length
1010  axes(diag_axis_id)%attributes(this_attribute)%iatt = ival
1011  CASE (nf90_float)
1012  IF ( .NOT.PRESENT(rval) ) THEN
1013  ! <ERROR STATUS="FATAL">
1014  ! Attribute type claims REAL, but rval not present for attribute <name> for axis <axis_name>.
1015  ! Contact the developers.
1016  ! </ERROR>
1017  CALL error_mesg('diag_manager_mod::diag_axis_add_attribute',&
1018  & 'Attribute type claims REAL, but rval not present for attribute "'&
1019  & //trim(name)//'" for axis "'//trim(axes(diag_axis_id)%name)&
1020  & //'". Contact the developers.', fatal)
1021  END IF
1022  length = SIZE(rval)
1023  ! Allocate iatt(:) to size of rval
1024  ALLOCATE(axes(diag_axis_id)%attributes(this_attribute)%fatt(length), stat=istat)
1025  IF ( istat.NE.0 ) THEN
1026  ! <ERROR STATUS="FATAL">
1027  ! Unable to allocate fatt for attribute <name> for axis <axis_name>
1028  ! </ERROR>
1029  CALL error_mesg('diag_manager_mod::diag_axis_add_attribute', 'Unable to allocate fatt for attribute "'&
1030  & //trim(name)//'" for axis "'//trim(axes(diag_axis_id)%name)&
1031  & //'"', fatal)
1032  END IF
1033  ! Set remaining fields
1034  axes(diag_axis_id)%attributes(this_attribute)%len = length
1035  axes(diag_axis_id)%attributes(this_attribute)%fatt = rval
1036  CASE (nf90_char)
1037  IF ( .NOT.PRESENT(cval) ) THEN
1038  ! <ERROR STATUS="FATAL">
1039  ! Attribute type claims CHARACTER, but cval not present for attribute <name> for axis <axis_name>.
1040  ! Contact the developers.
1041  ! </ERROR>
1042  CALL error_mesg('diag_manager_mod::diag_axis_add_attribute',&
1043  & 'Attribute type claims CHARACTER, but cval not present for attribute "'&
1044  & //trim(name)//'" for axis "'//trim(axes(diag_axis_id)%name)&
1045  & //'". Contact the developers.', fatal)
1046  END IF
1047  CALL prepend_attribute_axis(axes(diag_axis_id), trim(name), trim(cval))
1048  CASE default
1049  ! <ERROR STATUS="FATAL">
1050  ! Unknown attribute type for attribute <name> for axis <axis_name>.
1051  ! Contact the developers.
1052  ! </ERROR>
1053  CALL error_mesg('diag_manager_mod::diag_axis_add_attribute', 'Unknown attribute type for attribute "'&
1054  & //trim(name)//'" for axis "'//trim(axes(diag_axis_id)%name)&
1055  & //'". Contact the developers.', fatal)
1056  END SELECT
1057  END IF
1058  END SUBROUTINE diag_axis_attribute_init
1059 
1060  SUBROUTINE diag_axis_add_attribute_scalar_r(diag_axis_id, att_name, att_value)
1061  INTEGER, INTENT(in) :: diag_axis_id
1062  CHARACTER(len=*), INTENT(in) :: att_name
1063  REAL, INTENT(in) :: att_value
1064 
1065  if (use_modern_diag) then
1066  call fms_diag_object%fms_diag_axis_add_attribute(diag_axis_id, att_name, (/ att_value /))
1067  else
1068  CALL diag_axis_add_attribute_r1d(diag_axis_id, att_name, (/ att_value /))
1069  endif
1070  END SUBROUTINE diag_axis_add_attribute_scalar_r
1071 
1072  SUBROUTINE diag_axis_add_attribute_scalar_i(diag_axis_id, att_name, att_value)
1073  INTEGER, INTENT(in) :: diag_axis_id
1074  CHARACTER(len=*), INTENT(in) :: att_name
1075  INTEGER, INTENT(in) :: att_value
1076 
1077  if (use_modern_diag) then
1078  call fms_diag_object%fms_diag_axis_add_attribute(diag_axis_id, att_name, (/ att_value /))
1079  else
1080  CALL diag_axis_add_attribute_i1d(diag_axis_id, att_name, (/ att_value /))
1081  endif
1082  END SUBROUTINE diag_axis_add_attribute_scalar_i
1083 
1084  SUBROUTINE diag_axis_add_attribute_scalar_c(diag_axis_id, att_name, att_value)
1085  INTEGER, INTENT(in) :: diag_axis_id
1086  CHARACTER(len=*), INTENT(in) :: att_name
1087  CHARACTER(len=*), INTENT(in) :: att_value
1088 
1089  if (use_modern_diag) then
1090  call fms_diag_object%fms_diag_axis_add_attribute(diag_axis_id, att_name, (/ att_value /))
1091  else
1092  CALL diag_axis_attribute_init(diag_axis_id, att_name, nf90_char, cval=att_value)
1093  endif
1094  END SUBROUTINE diag_axis_add_attribute_scalar_c
1095 
1096  SUBROUTINE diag_axis_add_attribute_r1d(diag_axis_id, att_name, att_value)
1097  INTEGER, INTENT(in) :: diag_axis_id
1098  CHARACTER(len=*), INTENT(in) :: att_name
1099  REAL, DIMENSION(:), INTENT(in) :: att_value
1100 
1101  if (use_modern_diag) then
1102  call fms_diag_object%fms_diag_axis_add_attribute(diag_axis_id, att_name, att_value)
1103  else
1104  CALL diag_axis_attribute_init(diag_axis_id, att_name, nf90_float, rval=att_value)
1105  endif
1106  END SUBROUTINE diag_axis_add_attribute_r1d
1107 
1108  SUBROUTINE diag_axis_add_attribute_i1d(diag_axis_id, att_name, att_value)
1109  INTEGER, INTENT(in) :: diag_axis_id
1110  CHARACTER(len=*), INTENT(in) :: att_name
1111  INTEGER, DIMENSION(:), INTENT(in) :: att_value
1112  if (use_modern_diag) then
1113  call fms_diag_object%fms_diag_axis_add_attribute(diag_axis_id, att_name, att_value)
1114  else
1115  CALL diag_axis_attribute_init(diag_axis_id, att_name, nf90_int, ival=att_value)
1116  endif
1117  END SUBROUTINE diag_axis_add_attribute_i1d
1118 
1119  !> @brief Allocates memory in out_file for the attributes. Will <TT>FATAL</TT> if err_msg is not included
1120  !! in the subroutine call.
1121  SUBROUTINE attribute_init_axis(out_axis, err_msg)
1122  TYPE(diag_axis_type), INTENT(inout) :: out_axis !< output file to allocate memory for attribute
1123  CHARACTER(LEN=*), INTENT(out), OPTIONAL :: err_msg !< Error message, passed back to calling function
1124 
1125  INTEGER :: istat
1126 
1127  ! Initialize err_msg
1128  IF ( PRESENT(err_msg) ) err_msg = ''
1129 
1130  ! Allocate memory for the attributes
1131  IF ( .NOT.allocated(out_axis%attributes) ) THEN
1132  ALLOCATE(out_axis%attributes(max_axis_attributes), stat=istat)
1133  IF ( istat.NE.0 ) THEN
1134  ! <ERROR STATUS="FATAL">
1135  ! Unable to allocate memory for diag axis attributes
1136  ! </ERROR>
1137  IF ( fms_error_handler('diag_util_mod::attribute_init_axis', &
1138  & 'Unable to allocate memory for diag axis attributes', err_msg) ) THEN
1139  RETURN
1140  END IF
1141  ELSE
1142  ! Set equal to 0. It will be increased when attributes added
1143  out_axis%num_attributes = 0
1144  END IF
1145  END IF
1146  END SUBROUTINE attribute_init_axis
1147 
1148  !> @brief Prepends the attribute value to an already existing attribute. If the
1149  !! attribute isn't yet defined, then creates a new attribute
1150  SUBROUTINE prepend_attribute_axis(out_axis, att_name, prepend_value, err_msg)
1151  TYPE(diag_axis_type), INTENT(inout) :: out_axis !< diagnostic axis that will get the attribute
1152  CHARACTER(len=*), INTENT(in) :: att_name !< Name of the attribute
1153  CHARACTER(len=*), INTENT(in) :: prepend_value !< Value to prepend
1154  CHARACTER(len=*), INTENT(out) , OPTIONAL :: err_msg !< Error message, passed back to calling routine
1155 
1156  INTEGER :: length, i, this_attribute
1157  CHARACTER(len=512) :: err_msg_local
1158 
1159  ! Initialize string variables
1160  err_msg_local = ''
1161  IF ( PRESENT(err_msg) ) err_msg = ''
1162 
1163  ! Make sure the attributes for this out file have been initialized
1164  CALL attribute_init_axis(out_axis, err_msg_local)
1165  IF ( trim(err_msg_local) .NE. '' ) THEN
1166  IF ( fms_error_handler('diag_util_mod::prepend_attribute_axis', trim(err_msg_local), err_msg) ) THEN
1167  RETURN
1168  END IF
1169  END IF
1170 
1171  ! Find if attribute exists
1172  this_attribute = 0
1173  DO i=1, out_axis%num_attributes
1174  IF ( trim(out_axis%attributes(i)%name) .EQ. trim(att_name) ) THEN
1175  this_attribute = i
1176  EXIT
1177  END IF
1178  END DO
1179 
1180  IF ( this_attribute > 0 ) THEN
1181  IF ( out_axis%attributes(this_attribute)%type .NE. nf90_char ) THEN
1182  ! <ERROR STATUS="FATAL">
1183  ! Attribute <name> is not a character attribute.
1184  ! </ERROR>
1185  IF ( fms_error_handler('diag_util_mod::prepend_attribute_axis',&
1186  & 'Attribute "'//trim(att_name)//'" is not a character attribute.',&
1187  & err_msg) ) THEN
1188  RETURN
1189  END IF
1190  END IF
1191  ELSE
1192  ! Defining a new attribute
1193  ! Increase the number of file attributes
1194  this_attribute = out_axis%num_attributes + 1
1195  IF ( this_attribute .GT. max_axis_attributes ) THEN
1196  ! <ERROR STATUS="FATAL">
1197  ! Number of attributes exceeds max_axis_attributes for attribute <name>.
1198  ! Increase diag_manager_nml:max_axis_attributes.
1199  ! </ERROR>
1200  IF ( fms_error_handler('diag_util_mod::prepend_attribute_axis',&
1201  & 'Number of attributes exceeds max_axis_attributes for attribute "'&
1202  &//trim(att_name)//'". Increase diag_manager_nml:max_axis_attributes.',&
1203  & err_msg) ) THEN
1204  RETURN
1205  END IF
1206  ELSE
1207  out_axis%num_attributes = this_attribute
1208  ! Set name and type
1209  out_axis%attributes(this_attribute)%name = att_name
1210  out_axis%attributes(this_attribute)%type = nf90_char
1211  ! Initialize catt to a blank string, as len_trim doesn't always work on an uninitialized string
1212  out_axis%attributes(this_attribute)%catt = ''
1213  END IF
1214  END IF
1215 
1216  ! Only add string only if not already defined
1217  IF ( index(trim(out_axis%attributes(this_attribute)%catt), trim(prepend_value)).EQ.0 ) THEN
1218  ! Check if new string length goes beyond the length of catt
1219  length = len_trim(trim(prepend_value)//" "//trim(out_axis%attributes(this_attribute)%catt))
1220  IF ( length.GT.len(out_axis%attributes(this_attribute)%catt) ) THEN
1221  ! <ERROR STATUS="FATAL">
1222  ! Prepend length for attribute <name> is longer than allowed.
1223  ! </ERROR>
1224  IF ( fms_error_handler('diag_util_mod::prepend_attribute_file',&
1225  & 'Prepend length for attribute "'//trim(att_name)//'" is longer than allowed.',&
1226  & err_msg) ) THEN
1227  RETURN
1228  END IF
1229  END IF
1230  ! Set files
1231  out_axis%attributes(this_attribute)%catt =&
1232  & trim(prepend_value)//' '//trim(out_axis%attributes(this_attribute)%catt)
1233  out_axis%attributes(this_attribute)%len = length
1234  END IF
1235  END SUBROUTINE prepend_attribute_axis
1236 
1237  !> @brief given an axis, returns TRUE if the axis uses compression-by-gathering: that is, if
1238  !! this is an axis for fields on unstructured grid
1239  !! @return logical whether or not the axis uses compression-by-gathering
1240  logical function axis_is_compressed(id)
1241  integer, intent(in) :: id
1242 
1243  integer :: i
1244 
1245  CALL valid_id_check(id, 'axis_is_compressed')
1246 
1247  axis_is_compressed = .false.
1248  if (.not.allocated(axes(id)%attributes)) return
1249  do i = 1, axes(id)%num_attributes
1250  if (trim(axes(id)%attributes(i)%name)=='compress') then
1251  axis_is_compressed = .true.
1252  return
1253  endif
1254  enddo
1255  end function axis_is_compressed
1256 
1257 
1258  !> @brief given an index of compressed-by-gathering axis, return an array of axes used in
1259  !! compression. It is a fatal error to call it on axis that is not compressed
1260  subroutine get_compressed_axes_ids(id, r)
1261  integer, intent(in) :: id
1262  integer, intent(out), allocatable :: r(:)
1263 
1264  integer iatt, k, k1, k2, n
1265  logical :: space
1266 
1267  character(*), parameter :: tag = 'get_compressed_axes_ids'
1268 
1269  CALL valid_id_check(id, tag)
1270 
1271  associate(axis=>axes(id))
1272  if (.not.allocated(axis%attributes)) call error_mesg(tag, &
1273  'attempt to get compression dimensions from axis "'//trim(axis%name)// &
1274  & '" which is not compressed (does not have any attributes)', fatal)
1275 
1276  iatt = 0
1277  do k = 1,axis%num_attributes
1278  if (trim(axis%attributes(k)%name)=='compress') then
1279  iatt = k; exit ! from loop
1280  endif
1281  enddo
1282 
1283  if (iatt == 0) call error_mesg(tag, &
1284  'attempt to get compression dimensions from axis "'//trim(axis%name)//&
1285  '" which is not compressed (does not have "compress" attributes).', fatal)
1286  if (axis%attributes(iatt)%type/=nf90_char) call error_mesg(tag, &
1287  'attempt to get compression dimensions from axis "'//trim(axis%name)//&
1288  '" but the axis attribute "compress" has incorrect type.', fatal)
1289 
1290  ! parse the "compress" attribute
1291  ! calculate the number of compression axes
1292  space = .true.; n=0
1293  do k = 1, len(axis%attributes(iatt)%catt)
1294  if (space.and.(axis%attributes(iatt)%catt(k:k)/=' ')) then
1295  n = n+1
1296  endif
1297  space = (axis%attributes(iatt)%catt(k:k)==' ')
1298  enddo
1299 
1300  allocate(r(n))
1301  ! make array of compression axes indices. Go from the last to the first to get the
1302  ! array in FORTRAN order: they are listed in "compress" attribute C order (fastest
1303  ! dimension last)
1304  k2 = 0
1305  do k = n, 1, -1
1306  do k1 = k2+1, len(axis%attributes(iatt)%catt)
1307  if (axis%attributes(iatt)%catt(k1:k1)/=' ') exit
1308  enddo
1309  do k2 = k1+1, len(axis%attributes(iatt)%catt)
1310  if (axis%attributes(iatt)%catt(k2:k2)==' ') exit
1311  enddo
1312  r(k) = get_axis_num(axis%attributes(iatt)%catt(k1:k2),axis_sets(axis%set))
1313  if (r(k)<=0) call error_mesg(tag, &
1314  'compression dimension "'//trim(axis%attributes(iatt)%catt(k1:k2))//&
1315  '" not found among the axes of set "'//trim(axis_sets(axis%set))//'".', fatal)
1316  enddo
1317  end associate ! axis
1318  end subroutine get_compressed_axes_ids
1319 END MODULE diag_axis_mod
1320 !> @}
1321 ! close documentation grouping
subroutine, public get_compressed_axes_ids(id, r)
given an index of compressed-by-gathering axis, return an array of axes used in compression....
Definition: diag_axis.F90:1261
integer(i4_kind) function, public axis_compatible_check(id, varname)
Checks if the axes are compatible.
Definition: diag_axis.F90:744
integer function, public get_axis_length(id)
Return the length of the axis.
Definition: diag_axis.F90:611
subroutine, public get_diag_axis(id, name, units, long_name, cart_name, direction, edges, Domain, DomainU, array_data, num_attributes, attributes, domain_position)
Return information about the axis with index ID.
Definition: diag_axis.F90:467
subroutine, public get_diag_axis_cart(id, cart_name)
Return the axis cartesian.
Definition: diag_axis.F90:565
subroutine valid_id_check(id, routine_name)
Check to see if the given axis id is a valid id. If the axis id is invalid, call a FATAL error....
Definition: diag_axis.F90:884
integer function, public get_axis_num(axis_name, set_name)
Returns index into axis table corresponding to a given axis name.
Definition: diag_axis.F90:846
subroutine attribute_init_axis(out_axis, err_msg)
Allocates memory in out_file for the attributes. Will FATAL if err_msg is not included in the subrout...
Definition: diag_axis.F90:1122
integer function, public get_axis_global_length(id)
Return the global length of the axis.
Definition: diag_axis.F90:649
type(domain2d) function, public get_domain2d(ids)
Return the 2D domain for the axis IDs given.
Definition: diag_axis.F90:698
integer function get_axis_set_num(set_name)
Returns index in axis set table corresponding to a given axis set name.
Definition: diag_axis.F90:868
type(domainug) function, public get_domainug(id)
Retrun the 1D domain for the axis ID given.
Definition: diag_axis.F90:730
subroutine, public get_diag_axis_data(id, axis_data)
Return the axis data.
Definition: diag_axis.F90:574
character(len=128) function, public get_axis_reqfld(id)
Return the required field names for the axis.
Definition: diag_axis.F90:640
subroutine diag_axis_attribute_init(diag_axis_id, name, type, cval, ival, rval)
Definition: diag_axis.F90:900
integer function, public get_tile_count(ids)
Return the tile count for the axis.
Definition: diag_axis.F90:658
subroutine, public get_diag_axis_name(id, axis_name)
Return the short name of the axis.
Definition: diag_axis.F90:588
subroutine prepend_attribute_axis(out_axis, att_name, prepend_value, err_msg)
Prepends the attribute value to an already existing attribute. If the attribute isn't yet defined,...
Definition: diag_axis.F90:1151
character(len=128) function, public get_axis_aux(id)
Return the auxiliary name for the axis.
Definition: diag_axis.F90:631
integer(i4_kind), parameter, public diag_axis_2ddomain
For unstructured grid support.
Definition: diag_axis.F90:62
type(diag_axis_type), dimension(:), allocatable, save axes
global storage for all defined axes
Definition: diag_axis.F90:72
integer function, public diag_axis_init(name, array_data, units, cart_name, long_name, direction, set_name, edges, Domain, Domain2, DomainU, aux, req, tile_count, domain_position)
Initialize the axis, and return the axis ID.
Definition: diag_axis.F90:110
logical function, public axis_is_compressed(id)
given an axis, returns TRUE if the axis uses compression-by-gathering: that is, if this is an axis fo...
Definition: diag_axis.F90:1241
subroutine, public get_axes_shift(ids, ishift, jshift)
Return the value of the shift for the axis IDs given.
Definition: diag_axis.F90:822
integer, dimension(:), allocatable num_subaxes
counter of number of axes defined
Definition: diag_axis.F90:66
integer(i4_kind), parameter, public diag_axis_ugdomain
For unstructured grid support.
Definition: diag_axis.F90:63
subroutine, public get_diag_axis_domain_name(id, name)
Return the name of the axis' domain.
Definition: diag_axis.F90:601
type(domain1d) function, public get_domain1d(id)
Retrun the 1D domain for the axis ID given.
Definition: diag_axis.F90:685
integer function, public diag_subaxes_init(axis, subdata, start_indx, end_indx, domain_2d)
Create a subaxis on a parent axis.
Definition: diag_axis.F90:382
character(len=128), dimension(:), allocatable, save axis_sets
storage for axis set names
Definition: diag_axis.F90:69
Add an arbitrary attribute and value to the diagnostic axis.
Definition: diag_axis.F90:88
integer max_axis_attributes
Maximum number of user definable attributes per axis.
Definition: diag_data.F90:385
logical use_modern_diag
Namelist flag to use the modernized diag_manager code.
Definition: diag_data.F90:391
integer max_axes
Maximum number of independent axes.
Definition: diag_data.F90:361
Attribute type for diagnostic fields.
Definition: diag_data.F90:157
Type to hold the diagnostic axis description.
Definition: diag_data.F90:306
subroutine, public write_version_number(version, tag, unit)
Prints to the log file (or a specified unit) the version id string and tag name.
Definition: fms.F90:701
logical function, public fms_error_handler(routine, message, err_msg)
Facilitates the control of fatal error conditions.
Definition: fms.F90:468
subroutine, public error_mesg(routine, message, level)
Print notes, warnings and error messages; terminates program for warning and error messages....
Definition: fms.F90:441
subroutine mpp_get_domain_components(domain, x, y, tile_count)
Retrieve 1D components of 2D decomposition.
character(len=name_length) function mpp_get_domain_name(domain)
Set user stack size.
These routines retrieve the axis specifications associated with the compute domains....
These routines retrieve the axis specifications associated with the global domains....
One dimensional domain used to manage shared data access between pes.
The domain2D type contains all the necessary information to define the global, compute and data domai...
Domain information for managing data on unstructured grids.