FMS  2026.01.01-dev
Flexible Modeling System
fms_diag_axis_object.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 
19 !> @defgroup fms_diag_axis_object_mod fms_diag_axis_object_mod
20 !> @ingroup diag_manager
21 !! @brief fms_diag_axis_object_mod stores the diag axis object, a diag domain
22 !! object, and a subaxis object.
23 
24 !> @file
25 !> @brief File for @ref diag_axis_object_mod
26 
27 !> @addtogroup fms_diag_axis_object_mod
28 !> @{
29 module fms_diag_axis_object_mod
30 #ifdef use_yaml
31  use mpp_domains_mod, only: domain1d, domain2d, domainug, mpp_get_compute_domain, center, &
32  & mpp_get_global_domain, north, east, mpp_get_tile_id, &
34  use platform_mod, only: r8_kind, r4_kind, i4_kind, i8_kind
35  use diag_data_mod, only: diag_atttype, max_axes, no_domain, two_d_domain, ug_domain, &
37  diag_null, index_gridtype, latlon_gridtype, pack_size_str, &
40  use mpp_mod, only: fatal, mpp_error, uppercase, mpp_pe, mpp_root_pe, stdout, note
41  use fms2_io_mod, only: fmsnetcdffile_t, fmsnetcdfdomainfile_t, fmsnetcdfunstructureddomainfile_t, &
42  & register_axis, register_field, register_variable_attribute, write_data
43  use fms_diag_yaml_mod, only: subregion_type, diag_yaml, max_subaxes, diagyamlfilesvar_type
44  use diag_grid_mod, only: get_local_indices_cubesphere => get_local_indexes
45  use axis_utils2_mod, only: nearest_index
46  implicit none
47 
48  PRIVATE
49 
56 
57  !> @}
58 
59  !> @brief Type to hold the domain info for an axis
60  !! This type was created to avoid having to send in "Domain", "Domain2", "DomainUG" as arguments into subroutines
61  !! and instead only 1 class(diagDomain_t) argument can be send
62  !> @ingroup diag_axis_object_mod
64  contains
65  procedure :: set => set_axis_domain
66  procedure :: length => get_length
67  procedure :: get_ntiles
68  end type diagdomain_t
69 
70  !> @brief Type to hold the 1d domain
71  type, extends(diagdomain_t) :: diagdomain1d_t
72  type(domain1d) :: domain !< 1d Domain of the axis
73  end type
74 
75  !> @brief Type to hold the 2d domain
76  type, extends(diagdomain_t) :: diagdomain2d_t
77  type(domain2d) :: domain2 !< 2d Domain of an "X" or "Y" axis
78  end type
79 
80  !> @brief Type to hold the unstructured domain
81  type, extends(diagdomain_t) :: diagdomainug_t
82  type(domainug) :: domainug !< Domain of "U" axis
83  end type
84 
85  !> @brief Type to hold the diagnostic axis description.
86  !> @ingroup diag_axis_object_mod
88  INTEGER , private :: axis_id !< ID of the axis
89 
90  contains
91  procedure :: get_parent_axis_id
92  procedure :: get_subaxes_id
93  procedure :: get_axis_name
94  procedure :: is_z_axis
95  procedure :: write_axis_metadata
96  procedure :: write_axis_data
97  procedure :: add_structured_axis_ids
98  procedure :: get_structured_axis
99  procedure :: is_unstructured_grid
100  procedure :: get_edges_id
101  END TYPE fmsdiagaxis_type
102 
103  !> @brief Type to hold the diag_axis (either subaxis or a full axis)
104  !> @ingroup diag_axis_object_mod
106  class(fmsDiagAxis_type), allocatable :: axis
107  end type
108 
109  !> @brief Type to hold the subaxis
110  !> @ingroup diag_axis_object_mod
112  CHARACTER(len=:), ALLOCATABLE , private :: subaxis_name !< Name of the subaxis
113  INTEGER , private :: starting_index !< Starting index of the subaxis relative to the
114  !! parent axis
115  INTEGER , private :: ending_index !< Ending index of the subaxis relative to the
116  !! parent axis
117  INTEGER , private :: parent_axis_id !< Id of the parent_axis
118  INTEGER , private :: compute_idx(2) !< Starting and ending index of the compute domain
119  INTEGER, allocatable, private :: global_idx(:) !< Starting and ending index of the global domain
120  real(kind=r4_kind), allocatable, private :: zbounds(:) !< Bounds of the Z axis
121  contains
122  procedure :: fill_subaxis
123  procedure :: axis_length
124  procedure :: get_starting_index
125  procedure :: get_ending_index
126  procedure :: get_compute_indices
127  procedure :: is_same_zbounds
128  END TYPE fmsdiagsubaxis_type
129 
130  !> @brief Type to hold the diurnal axis
131  !> @ingroup diag_axis_object_mod
133  INTEGER , private :: ndiurnal_samples !< The number of diurnal samples
134  CHARACTER(len=:), ALLOCATABLE, private :: axis_name !< The diurnal axis name
135  CHARACTER(len=:), ALLOCATABLE, private :: long_name !< The longname of the diurnal axis
136  CHARACTER(len=:), ALLOCATABLE, private :: units !< The units
137  INTEGER , private :: edges_id !< The id of the diurnal edges
138  CHARACTER(len=:), ALLOCATABLE, private :: edges_name !< The name of the edges axis
139  CLASS(*), ALLOCATABLE, private :: diurnal_data(:) !< The diurnal data
140 
141  contains
142  procedure :: get_diurnal_axis_samples
143  procedure :: write_diurnal_metadata
144  END TYPE fmsdiagdiurnalaxis_type
145 
146  !> @brief Type to hold the diagnostic axis description.
147  !> @ingroup diag_axis_object_mod
149  CHARACTER(len=:), ALLOCATABLE, private :: axis_name !< Name of the axis
150  CHARACTER(len=:), ALLOCATABLE, private :: units !< Units of the axis
151  CHARACTER(len=:), ALLOCATABLE, private :: long_name !< Long_name attribute of the axis
152  CHARACTER(len=1) , private :: cart_name !< Cartesian name "X", "Y", "Z", "T", "U", "N"
153  CLASS(*), ALLOCATABLE, private :: axis_data(:) !< Data of the axis
154  CHARACTER(len=:), ALLOCATABLE, private :: type_of_data !< The type of the axis_data ("float" or "double")
155  !< TO DO this can be a dlinked to avoid having limits
156  integer, ALLOCATABLE, private :: subaxis(:) !< Array of subaxis
157  integer , private :: nsubaxis !< Number of subaxis
158  class(diagdomain_t),ALLOCATABLE, private :: axis_domain !< Domain
159  INTEGER , private :: type_of_domain !< The type of domain ("NO_DOMAIN", "TWO_D_DOMAIN",
160  !! or "UG_DOMAIN")
161  INTEGER , private :: length !< Global axis length
162  INTEGER , private :: direction !< Direction of the axis 0, 1, -1
163  INTEGER, ALLOCATABLE, private :: edges_id !< Axis ID for the edges axis
164  !! This axis will be written to the file
165  CHARACTER(len=:), ALLOCATABLE, private :: edges_name !< Name for the previously defined "edges axis"
166  !! This will be written as an attribute
167  CHARACTER(len=:), ALLOCATABLE, private :: aux !< Auxiliary name, can only be <TT>geolon_t</TT>
168  !! or <TT>geolat_t</TT>
169  CHARACTER(len=128) , private :: req !< Required field names.
170  INTEGER , private :: tile_count !< The number of tiles
171  TYPE(fmsdiagattribute_type),allocatable , private :: attributes(:) !< Array to hold user definable attributes
172  INTEGER , private :: num_attributes !< Number of defined attibutes
173  INTEGER , private :: domain_position !< The position in the doman (NORTH, EAST or CENTER)
174  integer, allocatable , private :: structured_ids(:) !< If the axis is in the unstructured grid,
175  !! this is the axis ids of the structured axis
176  CHARACTER(len=:), ALLOCATABLE, private :: set_name !< Name of the axis set. This is to distinguish
177  !! two axis with the same name
178 
179  contains
180 
181  PROCEDURE :: add_axis_attribute
182  PROCEDURE :: register => register_diag_axis_obj
183  PROCEDURE :: axis_length => get_axis_length
184  PROCEDURE :: set_edges
185  PROCEDURE :: set_axis_id
186  PROCEDURE :: get_compute_domain
187  PROCEDURE :: get_indices
188  PROCEDURE :: get_global_io_domain
189  PROCEDURE :: get_aux
190  PROCEDURE :: has_aux
191  PROCEDURE :: get_set_name
192  PROCEDURE :: has_set_name
193  PROCEDURE :: is_x_or_y_axis
194  PROCEDURE :: get_dim_size_layout
195  ! TO DO:
196  ! Get/has/is subroutines as needed
197  END TYPE fmsdiagfullaxis_type
198 
199  !> @addtogroup fms_diag_yaml_mod
200  !> @{
201  contains
202 
203  !!!!!!!!!!!!!!!!! DIAG AXIS PROCEDURES !!!!!!!!!!!!!!!!!
204  !> @brief Initialize the axis
205  subroutine register_diag_axis_obj(this, axis_name, axis_data, units, cart_name, long_name, direction,&
206  & set_name, Domain, Domain2, DomainU, aux, req, tile_count, domain_position, axis_length )
207  class(fmsdiagfullaxis_type),INTENT(inout):: this !< Diag_axis obj
208  CHARACTER(len=*), INTENT(in) :: axis_name !< Name of the axis
209  class(*), INTENT(in) :: axis_data(:) !< Array of coordinate values
210  CHARACTER(len=*), INTENT(in) :: units !< Units for the axis
211  CHARACTER(len=1), INTENT(in) :: cart_name !< Cartesian axis ("X", "Y", "Z", "T", "U", "N")
212  CHARACTER(len=*), INTENT(in), OPTIONAL :: long_name !< Long name for the axis.
213  CHARACTER(len=*), INTENT(in), OPTIONAL :: set_name !< Name of the parent axis, if it is a subaxis
214  INTEGER, INTENT(in), OPTIONAL :: direction !< Indicates the direction of the axis
215  TYPE(domain1d), INTENT(in), OPTIONAL :: Domain !< 1D domain
216  TYPE(domain2d), INTENT(in), OPTIONAL :: Domain2 !< 2D domain
217  TYPE(domainug), INTENT(in), OPTIONAL :: DomainU !< Unstructured domain
218  CHARACTER(len=*), INTENT(in), OPTIONAL :: aux !< Auxiliary name, can only be <TT>geolon_t</TT>
219  !! or <TT>geolat_t</TT>
220  CHARACTER(len=*), INTENT(in), OPTIONAL :: req !< Required field names.
221  INTEGER, INTENT(in), OPTIONAL :: tile_count !< Number of tiles
222  INTEGER, INTENT(in), OPTIONAL :: domain_position !< Domain position, "NORTH" or "EAST"
223  integer, intent(in), optional :: axis_length !< The length of the axis size(axis_data(:))
224 
225  this%axis_name = trim(axis_name)
226  this%units = trim(units)
227  this%cart_name = uppercase(cart_name)
228  call check_if_valid_cart_name(this%cart_name)
229 
230  if (present(long_name)) this%long_name = trim(long_name)
231 
232  select type (axis_data)
233  type is (real(kind=r8_kind))
234  allocate(real(kind=r8_kind) :: this%axis_data(axis_length))
235  this%axis_data = axis_data
236  this%length = axis_length
237  this%type_of_data = "double" !< This is what fms2_io expects in the register_field call
238  type is (real(kind=r4_kind))
239  allocate(real(kind=r4_kind) :: this%axis_data(axis_length))
240  this%axis_data = axis_data
241  this%length = axis_length
242  this%type_of_data = "float" !< This is what fms2_io expects in the register_field call
243  class default
244  call mpp_error(fatal, "The axis_data in your diag_axis_init call is not a supported type. &
245  & Currently only r4 and r8 data is supported.")
246  end select
247 
248  this%type_of_domain = no_domain
249  if (present(domain)) then
250  if (present(domain2) .or. present(domainu)) call mpp_error(fatal, &
251  "The presence of Domain with any other domain type is prohibited. "//&
252  "Check you diag_axis_init call for axis_name:"//trim(axis_name))
253  allocate(diagdomain1d_t :: this%axis_domain)
254  call this%axis_domain%set(domain=domain)
255  else if (present(domain2)) then
256  if (present(domainu)) call mpp_error(fatal, &
257  "The presence of Domain2 with any other domain type is prohibited. "//&
258  "Check you diag_axis_init call for axis_name:"//trim(axis_name))
259  allocate(diagdomain2d_t :: this%axis_domain)
260  call this%axis_domain%set(domain2=domain2)
261  this%type_of_domain = two_d_domain
262  else if (present(domainu)) then
263  allocate(diagdomainug_t :: this%axis_domain)
264  call this%axis_domain%set(domainu=domainu)
265  this%type_of_domain = ug_domain
266  endif
267 
268  this%tile_count = 1
269  if (present(tile_count)) this%tile_count = tile_count
270 
271  this%domain_position = center
272  if (present(domain_position)) this%domain_position = domain_position
273  call check_if_valid_domain_position(this%domain_position)
274 
275  this%direction = 0
276  if (present(direction)) this%direction = direction
277  call check_if_valid_direction(this%direction)
278 
279  if (present(aux)) this%aux = trim(aux)
280  if (present(req)) this%req = trim(req)
281  this%set_name = ""
282  if (present(set_name)) this%set_name = trim(set_name)
283 
284  if (max_subaxes .gt. 0) then
285  allocate(this%subaxis(max_subaxes))
286  this%subaxis = diag_null
287  endif
288 
289  this%nsubaxis = 0
290  this%num_attributes = 0
291  end subroutine register_diag_axis_obj
292 
293  !> @brief Add an attribute to an axis
294  subroutine add_axis_attribute(this, att_name, att_value)
295  class(fmsdiagfullaxis_type),INTENT(INOUT) :: this !< diag_axis obj
296  character(len=*), intent(in) :: att_name !< Name of the attribute
297  class(*), intent(in) :: att_value(:) !< The attribute value to add
298 
299  integer :: j !< obj%num_attributes (for less typing)
300 
301  if (.not. allocated(this%attributes)) &
302  allocate(this%attributes(max_axis_attributes))
303 
304  this%num_attributes = this%num_attributes + 1
305 
306  j = this%num_attributes
307  call this%attributes(j)%add(att_name, att_value)
308  end subroutine add_axis_attribute
309 
310  !> @brief Write the axis meta data to an open fileobj
311  subroutine write_axis_metadata(this, fms2io_fileobj, edges_in_file, parent_axis)
312  class(fmsdiagaxis_type), target, INTENT(IN) :: this !< diag_axis obj
313  class(fmsnetcdffile_t), INTENT(INOUT) :: fms2io_fileobj!< Fms2_io fileobj to write the data to
314  logical, INTENT(IN) :: edges_in_file !< .True. if the edges to this axis are
315  !! already in the file
316  class(fmsdiagaxis_type), OPTIONAL, target, INTENT(IN) :: parent_axis !< If the axis is a subaxis, axis object
317  !! for the parent axis (this will be used
318  !! to get some of the metadata info)
319 
320  character(len=:), ALLOCATABLE :: axis_edges_name !< Name of the edges, if it exist
321  character(len=:), pointer :: axis_name !< Name of the axis
322  integer :: axis_length !< Size of the axis
323  integer :: i !< For do loops
324  type(fmsdiagfullaxis_type), pointer :: diag_axis !< Local pointer to the diag_axis
325 
326  integer :: type_of_domain !< The type of domain the current axis is in
327  logical :: is_subaxis !< .true. if the axis is a subaxis
328  logical :: needs_domain_decomposition !< .True. if the axis needs the domain decomposition attribute
329  !! (i.e for "X" and "Y" subaxis)
330  integer :: domain_decomposition(4) !< indices of the global (1:2) and compute (3:4) domain for a "X" and "Y" subaxis
331 
332  is_subaxis = .false.
333  needs_domain_decomposition = .false.
334 
335  select type(this)
336  type is (fmsdiagfullaxis_type)
337  axis_name => this%axis_name
338  axis_length = this%length
339  diag_axis => this
340  type_of_domain = this%type_of_domain
341  type is (fmsdiagsubaxis_type)
342  is_subaxis = .true.
343  axis_name => this%subaxis_name
344  axis_length = this%ending_index - this%starting_index + 1
345  if (allocated(this%global_idx)) then
346  needs_domain_decomposition = .true.
347  domain_decomposition(1:2) = this%global_idx
348  domain_decomposition(3) = this%starting_index
349  domain_decomposition(4) = this%ending_index
350  endif
351  !< Get all the other information from the parent axis (i.e the cart_name, units, etc)
352  if (present(parent_axis)) then
353  select type(parent_axis)
354  type is (fmsdiagfullaxis_type)
355  diag_axis => parent_axis
356  end select
357  endif
358  type_of_domain = no_domain !< All subaxes are treated as non-domain decomposed (each rank writes it own file)
359  type is (fmsdiagdiurnalaxis_type)
360  call this%write_diurnal_metadata(fms2io_fileobj)
361  return
362  end select
363 
364  !< Add the axis as a dimension in the netcdf file based on the type of axis_domain and the fileobj type
365  select type (fms2io_fileobj)
366  !< The register_field calls need to be inside the select type block so that it can go inside the correct
367  !! register_field interface
368  type is (fmsnetcdffile_t)
369  !< Here the axis is not domain decomposed (i.e z_axis)
370  call register_axis(fms2io_fileobj, axis_name, axis_length)
371  call register_field(fms2io_fileobj, axis_name, diag_axis%type_of_data, (/axis_name/))
372  if (needs_domain_decomposition) then
373  call register_variable_attribute(fms2io_fileobj, axis_name, "domain_decomposition", &
374  domain_decomposition)
375  endif
376  type is (fmsnetcdfdomainfile_t)
377  select case (type_of_domain)
378  case (no_domain)
379  !< Here the fms2io_fileobj is domain decomposed, but the axis is not
380  !! Domain decomposed fileobjs can have axis that are not domain decomposed (i.e "Z" axis)
381  call register_axis(fms2io_fileobj, axis_name, axis_length)
382  call register_field(fms2io_fileobj, axis_name, diag_axis%type_of_data, (/axis_name/))
383  case (two_d_domain)
384  !< Here the axis is domain decomposed
385  call register_axis(fms2io_fileobj, axis_name, diag_axis%cart_name, domain_position=diag_axis%domain_position)
386  call register_field(fms2io_fileobj, axis_name, diag_axis%type_of_data, (/axis_name/))
387  end select
388  type is (fmsnetcdfunstructureddomainfile_t)
389  select case (type_of_domain)
390  case (ug_domain)
391  !< Here the axis is in a unstructured domain
392  call register_axis(fms2io_fileobj, axis_name)
393  call register_field(fms2io_fileobj, axis_name, diag_axis%type_of_data, (/axis_name/))
394  case default
395  !< Here the fms2io_fileobj is in the unstructured domain, but the axis is not
396  !< Unstructured domain fileobjs can have axis that are not domain decomposed (i.e "Z" axis)
397  call register_axis(fms2io_fileobj, axis_name, axis_length)
398  call register_field(fms2io_fileobj, axis_name, diag_axis%type_of_data, (/axis_name/))
399  end select
400  end select
401 
402  !< Write its metadata
403  if(allocated(diag_axis%long_name)) &
404  call register_variable_attribute(fms2io_fileobj, axis_name, "long_name", diag_axis%long_name, &
405  str_len=len_trim(diag_axis%long_name))
406 
407  if (diag_axis%cart_name .NE. "N") &
408  call register_variable_attribute(fms2io_fileobj, axis_name, "axis", diag_axis%cart_name, str_len=1)
409 
410  if (trim(diag_axis%units) .NE. "none") &
411  call register_variable_attribute(fms2io_fileobj, axis_name, "units", diag_axis%units, &
412  str_len=len_trim(diag_axis%units))
413 
414  select case (diag_axis%direction)
415  case (direction_up)
416  call register_variable_attribute(fms2io_fileobj, axis_name, "positive", "up", str_len=2)
417  case (direction_down)
418  call register_variable_attribute(fms2io_fileobj, axis_name, "positive", "down", str_len=4)
419  end select
420 
421  !< Ignore the edges attribute, if the edges are already in the file or if it is subaxis
422  if (.not. edges_in_file .and. allocated(diag_axis%edges_name) .and. .not. is_subaxis) then
423  call register_variable_attribute(fms2io_fileobj, axis_name, "edges", diag_axis%edges_name, &
424  str_len=len_trim(diag_axis%edges_name))
425  endif
426 
427  if(allocated(diag_axis%attributes)) then
428  do i = 1, diag_axis%num_attributes
429  select type (att_value => diag_axis%attributes(i)%att_value)
430  type is (character(len=*))
431  call register_variable_attribute(fms2io_fileobj, axis_name, diag_axis%attributes(i)%att_name, &
432  trim(att_value(1)), str_len=len_trim(att_value(1)))
433  class default
434  call register_variable_attribute(fms2io_fileobj, axis_name, diag_axis%attributes(i)%att_name, att_value)
435  end select
436  enddo
437  endif
438 
439  end subroutine write_axis_metadata
440 
441  !> @brief Write the axis data to an open fms2io_fileobj
442  subroutine write_axis_data(this, fms2io_fileobj, parent_axis)
443  class(fmsdiagaxis_type), target, INTENT(IN) :: this !< diag_axis obj
444  class(fmsnetcdffile_t), INTENT(INOUT) :: fms2io_fileobj!< Fms2_io fileobj to write the data to
445  class(fmsdiagaxis_type), OPTIONAL, target, INTENT(IN) :: parent_axis !< The parent axis if this is a subaxis
446 
447  integer :: i !< Starting index of a sub_axis
448  integer :: j !< Ending index of a sub_axis
449  integer :: global_io_index(2)!< Global io domain starting and ending index
450  select type(this)
451  type is (fmsdiagfullaxis_type)
452  call this%get_global_io_domain(global_io_index, fms2io_fileobj%is_file_using_netcdf_mpi())
453  call write_data(fms2io_fileobj, this%axis_name, this%axis_data(global_io_index(1):global_io_index(2)))
454  type is (fmsdiagsubaxis_type)
455  i = this%starting_index
456  j = this%ending_index
457 
458  if (present(parent_axis)) then
459  select type(parent_axis)
460  type is (fmsdiagfullaxis_type)
461  ! Added this select type so the the data is indexed correctly
462  select type (vardata => parent_axis%axis_data)
463  type is (real(kind=r8_kind))
464  call write_data(fms2io_fileobj, this%subaxis_name, vardata(i:j))
465  type is (real(kind=r4_kind))
466  call write_data(fms2io_fileobj, this%subaxis_name, vardata(i:j))
467  end select
468  end select
469  endif
470  type is (fmsdiagdiurnalaxis_type)
471  call write_data(fms2io_fileobj, this%axis_name, this%diurnal_data)
472  end select
473  end subroutine write_axis_data
474 
475 
476  !> @brief Defined a new diurnal axis
477  subroutine define_diurnal_axis(diag_axis, naxis, n_diurnal_samples, is_edges)
478  class(fmsdiagaxiscontainer_type), target, intent(inout) :: diag_axis(:) !< Array of axis containers
479  integer, intent(inout) :: naxis !< Number of axis that have
480  !! been defined
481  integer, intent(in) :: n_diurnal_samples !< The number of diurnal samples
482  !! for the curent axis
483  logical, intent(in) :: is_edges !< Flag indicating if this is
484  !! an edge axis
485 
486  CHARACTER(32) :: axis_name !< name of the axis
487  CHARACTER(32) :: long_name !< long name of the axis
488  CHARACTER(32) :: edges_name !< name of the axis edge
489  CHARACTER(128) :: units !< units of the axis
490  real(kind=r8_kind), allocatable :: diurnal_data(:) !< Data for the axis
491  integer :: edges_id !< Id of the axis edge
492  integer :: i !< For do loops
493 
494  naxis = naxis + 1
495 
496  axis_name = ''
497  edges_name = ''
498  if (is_edges) then
499  WRITE (axis_name,'(a,i2.2)') 'time_of_day_edges_', n_diurnal_samples
500  long_name = "time of day edges"
501  allocate(diurnal_data(n_diurnal_samples + 1))
502  diurnal_data(1) = 0.0
503  edges_id = diag_null
504  do i = 1, n_diurnal_samples
505  diurnal_data(i+1) = 24.0* real(i)/n_diurnal_samples
506  enddo
507  else
508  WRITE (axis_name,'(a,i2.2)') 'time_of_day_', n_diurnal_samples
509  long_name = "time of day"
510  allocate(diurnal_data(n_diurnal_samples))
511  edges_id = naxis -1 !< The diurnal edges is the last defined axis
512  do i = 1, n_diurnal_samples
513  diurnal_data(i) = 24.0*(real(i)-0.5)/n_diurnal_samples
514  enddo
515  WRITE (edges_name,'(a,i2.2)') 'time_of_day_edges_', n_diurnal_samples
516  endif
517 
518  WRITE (units,11) 'hours', get_base_year(), get_base_month(), &
520 11 FORMAT(a,' since ',i4.4,'-',i2.2,'-',i2.2,' ',i2.2,':',i2.2,':',i2.2)
521 
522  allocate(fmsdiagdiurnalaxis_type :: diag_axis(naxis)%axis)
523  select type (diurnal_axis => diag_axis(naxis)%axis)
524  type is (fmsdiagdiurnalaxis_type)
525  diurnal_axis%axis_id = naxis
526  diurnal_axis%ndiurnal_samples = n_diurnal_samples
527  diurnal_axis%axis_name = trim(axis_name)
528  diurnal_axis%long_name = trim(long_name)
529  diurnal_axis%units = trim(units)
530  diurnal_axis%diurnal_data = diurnal_data
531  diurnal_axis%edges_id = edges_id
532  if (is_edges) &
533  WRITE (edges_name,'(a,i2.2)') 'time_of_day_edges_', n_diurnal_samples
534  diurnal_axis%edges_name = trim(edges_name)
535  end select
536  end subroutine define_diurnal_axis
537 
538  !< @brief Determine if the axis is in the unstructured grid
539  !! @return .True. if the axis is in unstructured grid
540  pure logical function is_unstructured_grid(this)
541  class(fmsdiagaxis_type), target, INTENT(in) :: this !< diag_axis obj
542 
543  is_unstructured_grid = .false.
544  select type (this)
545  type is (fmsdiagfullaxis_type)
546  is_unstructured_grid = trim(this%cart_name) .eq. "U"
547  end select
548  end function is_unstructured_grid
549 
550  !< @brief Adds the structured axis ids to the axis object
551  subroutine add_structured_axis_ids(this, axis_ids)
552  class(fmsdiagaxis_type), target, INTENT(inout) :: this !< diag_axis obj
553  integer, intent(in) :: axis_ids(2) !< axis ids to add to the axis object
554 
555  select type (this)
556  type is (fmsdiagfullaxis_type)
557  allocate(this%structured_ids(2))
558  this%structured_ids = axis_ids
559  end select
560  end subroutine add_structured_axis_ids
561 
562  !< @brief Get the structured axis ids from the axis object
563  !! @return the structured axis ids
564  pure function get_structured_axis(this) &
565  result(rslt)
566  class(fmsdiagaxis_type), target, INTENT(in) :: this !< diag_axis obj
567  integer :: rslt(2)
568 
569  rslt = diag_null
570  select type (this)
571  type is (fmsdiagfullaxis_type)
572  rslt = this%structured_ids
573  end select
574  end function get_structured_axis
575 
576 
577  !< @brief Get the edges_id of an axis_object
578  !! @return The edges_id of an axis object
579  pure integer function get_edges_id(this)
580  class(fmsdiagaxis_type), INTENT(in) :: this !< diag_axis obj
581 
582  get_edges_id = diag_null
583  select type (this)
584  type is (fmsdiagfullaxis_type)
585  if (allocated(this%edges_id)) get_edges_id = this%edges_id
586  end select
587  end function
588 
589  !> @brief Get the starting and ending indices of the global io domain of the axis
590  subroutine get_global_io_domain(this, global_io_index, use_collective_writes)
591  class(fmsdiagfullaxis_type), target, intent(in) :: this !< diag_axis obj
592  integer, intent(out) :: global_io_index(2) !< Global io domain starting and ending index
593  logical, intent(in) :: use_collective_writes !< .True. if using collective writes
594 
595  type(domain2d), pointer :: io_domain !< pointer to the io domain
596 
597  global_io_index(1) = 1
598  global_io_index(2) = this%length
599 
600  if (allocated(this%axis_domain)) then
601  select type(domain => this%axis_domain)
602  type is (diagdomain2d_t)
603  if (use_collective_writes) then
604  io_domain => domain%domain2
605  else
606  io_domain => mpp_get_io_domain(domain%domain2)
607  endif
608 
609  if (this%cart_name .eq. "X") then
610  call mpp_get_global_domain(io_domain, xbegin=global_io_index(1), xend=global_io_index(2), &
611  position=this%domain_position)
612  elseif (this%cart_name .eq. "Y") then
613  call mpp_get_global_domain(io_domain, ybegin=global_io_index(1), yend=global_io_index(2), &
614  position=this%domain_position)
615  endif
616  end select
617  endif
618  end subroutine get_global_io_domain
619 
620  !> @brief Get the length of the axis
621  !> @return axis length
622  function get_axis_length(this) &
623  result(axis_length)
624  class(fmsdiagfullaxis_type), intent(in) :: this !< diag_axis obj
625  integer :: axis_length
626 
627  !< If the axis is domain decomposed axis_length will be set to the length for the current PE:
628  if (allocated(this%axis_domain)) then
629  axis_length = this%axis_domain%length(this%cart_name, this%domain_position, this%length)
630  else
631  axis_length = this%length
632  endif
633 
634  end function
635 
636 
637  !> @brief Determine if an axis object has an auxiliary name
638  !! @return .true. if an axis object has an auxiliary name
639  pure function has_aux(this) &
640  result(rslt)
641  class(fmsdiagfullaxis_type), intent(in) :: this !< diag_axis obj
642  logical :: rslt
643 
644  rslt = .false.
645  if (allocated(this%aux)) rslt = trim(this%aux) .ne. ""
646  end function has_aux
647 
648  !> @brief Determine if an axis object has a set_name
649  !! @return .true. if an axis object has a set_name
650  pure function has_set_name(this) &
651  result(rslt)
652  class(fmsdiagfullaxis_type), intent(in) :: this !< diag_axis obj
653  logical :: rslt
654 
655  rslt = .false.
656  if (allocated(this%set_name)) rslt = trim(this%set_name) .ne. ""
657  end function has_set_name
658 
659  !> @brief Determine if an axis object is an x or y axis
660  !! @return .true. if an axis object is an x or y axis, optionally return a flag indicating which it is
661  function is_x_or_y_axis(this, x_or_y) &
662  result(rslt)
663  class(fmsdiagfullaxis_type), intent(in) :: this !< diag_axis obj
664  integer, optional, intent(inout) :: x_or_y !< returns is_x_axis if it is a x axis
665  !! is_y_axis if it is a y axis
666  logical :: rslt
667 
668  select case (trim(this%cart_name))
669  case ("X")
670  if (present(x_or_y)) x_or_y = is_x_axis
671  rslt = .true.
672  case ("Y")
673  if (present(x_or_y)) x_or_y = is_y_axis
674  rslt = .true.
675  case default
676  rslt = .false.
677  if (present(x_or_y)) x_or_y = diag_null
678  end select
679  end function is_x_or_y_axis
680 
681  !< @brief Get the global size of the axis, and the layout
682  !! It is assumed that this function is only called on "X" and "Y" axes
683  !! using the `is_x_or_y_axis` function from above
684  subroutine get_dim_size_layout(this, dim_size, layout)
685  class(fmsdiagfullaxis_type), intent(in) :: this !< diag_axis obj
686  integer, intent(out) :: dim_size !< Size of the dimension
687  integer, intent(out) :: layout !< Layout of the dimension
688 
689  integer :: nx, ny
690  integer :: layout_xy(2)
691 
692  select type (domain => this%axis_domain)
693  type is (diagdomain2d_t)
694  call mpp_get_global_domain(domain%Domain2, xsize=nx, ysize=ny)
695  call mpp_get_layout(domain%Domain2, layout_xy)
696 
697  if (this%cart_name .eq. "X") then
698  dim_size = nx
699  layout = layout_xy(1)
700  else if (this%cart_name .eq. "Y") then
701  dim_size = ny
702  layout = layout_xy(2)
703  endif
704  end select
705  end subroutine get_dim_size_layout
706 
707  !> @brief Get the set name of an axis object
708  !! @return the set name of an axis object
709  pure function get_set_name(this) &
710  result(rslt)
711  class(fmsdiagfullaxis_type), intent(in) :: this !< diag_axis obj
712  character(len=:), allocatable :: rslt
713 
714  rslt = this%set_name
715  end function get_set_name
716 
717  !> @brief Get the auxiliary name of an axis object
718  !! @return the auxiliary name of an axis object
719  pure function get_aux(this) &
720  result(rslt)
721  class(fmsdiagfullaxis_type), intent(in) :: this !< diag_axis obj
722  character(len=:), allocatable :: rslt
723 
724  rslt = this%aux
725  end function get_aux
726 
727  !> @brief Set the axis_id
728  subroutine set_axis_id(this, axis_id)
729  class(fmsdiagfullaxis_type), intent(inout) :: this !< diag_axis obj
730  integer, intent(in) :: axis_id !< Axis_id
731 
732  this%axis_id = axis_id
733 
734  end subroutine set_axis_id
735 
736  !> @brief Set the name and ids of the edges
737  subroutine set_edges(this, edges_name, edges_id)
738  class(fmsdiagfullaxis_type), intent(inout) :: this !< diag_axis obj
739  CHARACTER(len=*), intent(in) :: edges_name !< Name of the edges
740  integer, intent(in) :: edges_id !< Axis id of the edges
741 
742  !< Saving the name and the id of the edges axis because it will make it easier to use
743  !! downstream (i.e you need the edges name to write the attribute to the current axis,
744  !! and you need the edges id to add to the diag file object so that you can write the edges
745  !! to the file)
746  this%edges_name = edges_name
747  this%edges_id = edges_id
748  end subroutine set_edges
749 
750  !> @brief Determine if the subRegion is in the current PE.
751  !! If it is, determine the starting and ending indices of the current PE that belong to the subRegion
752  subroutine get_indices(this, compute_idx, corners_indices, starting_index, ending_index, need_to_define_axis)
753  class(fmsdiagfullaxis_type), intent(in) :: this !< diag_axis obj
754  integer, intent(in) :: compute_idx(:) !< Current PE's compute domain
755  class(*), intent(in) :: corners_indices(:) !< The indices of the corners of the subRegion
756  integer, intent(out) :: starting_index !< Starting index of the subRegion
757  !! for the current PE
758  integer, intent(out) :: ending_index !< Ending index of the subRegion
759  !! for the current PE
760  logical, intent(out) :: need_to_define_axis !< .true. if it is needed to define
761  !! an axis
762 
763  integer :: subregion_start !< Starting index of the subRegion
764  integer :: subregion_end !< Ending index of the subRegion
765 
766  !< Get the rectangular coordinates of the subRegion
767  !! If the subRegion is not rectangular, the points outside of the subRegion will be masked
768  !! out later
769  select type (corners_indices)
770  type is (integer(kind=i4_kind))
771  subregion_start = minval(corners_indices)
772  subregion_end = maxval(corners_indices)
773  end select
774 
775  !< Initiliaze the output
776  need_to_define_axis = .false.
777  starting_index = diag_null
778  ending_index = diag_null
779 
780  !< If the compute domain of the current PE is outisde of the range of sub_axis, return
781  if (compute_idx(1) < subregion_start .and. compute_idx(2) < subregion_start) return
782  if (compute_idx(1) > subregion_end .and. compute_idx(2) > subregion_end) return
783 
784  need_to_define_axis = .true.
785  if (compute_idx(1) >= subregion_start .and. compute_idx(2) >= subregion_end) then
786  !< In this case all the point of the current PE are inside the range of the sub_axis
787  starting_index = compute_idx(1)
788  ending_index = subregion_end
789  else if (compute_idx(1) >= subregion_start .and. compute_idx(2) <= subregion_end) then
790  !< In this case all the points of the current PE are valid up to the end point
791  starting_index = compute_idx(1)
792  ending_index = compute_idx(2)
793  else if (compute_idx(1) <= subregion_start .and. compute_idx(2) <= subregion_end) then
794  !< In this case all the points of the current PE are valid starting with t subregion_start
795  starting_index = subregion_start
796  ending_index = compute_idx(2)
797  else if (compute_idx(1) <= subregion_start .and. compute_idx(2) >= subregion_end) then
798  !< In this case only the points in the current PE ar valid
799  starting_index = subregion_start
800  ending_index = subregion_end
801  endif
802 
803  if (this%domain_position .ne. center) then
804  if (subregion_end - subregion_start + 1 .eq. 1) then
805  !< If your subregion consitsts of just 1 one, only include 1 PE
806  if (ending_index .eq. compute_idx(2)) need_to_define_axis = .false.
807  else
808  if (ending_index - starting_index + 1 .eq. 1) then
809  !< If the PEs section is only 1, only include 1 PE
810  if (starting_index .eq. compute_idx(2) .or. ending_index .eq. compute_idx(1)) &
811  need_to_define_axis = .false.
812  endif
813  endif
814  endif
815 
816  end subroutine get_indices
817 
818  !< Get the compute domain of the axis
819  subroutine get_compute_domain(this, compute_idx, need_to_define_axis, tile_number)
820  class(fmsdiagfullaxis_type), intent(in) :: this !< diag_axis obj
821  integer, intent(inout) :: compute_idx(:) !< Compute domain of the axis
822  logical, intent(out) :: need_to_define_axis !< .true. if it needed to define the axis
823  integer, optional, intent(in) :: tile_number !< The tile number of the axis
824 
825  !< Initialize the output
826  need_to_define_axis = .false.
827  compute_idx = diag_null
828 
829  if (.not. allocated(this%axis_domain)) then
830  !< If the axis is not domain decomposed, use the whole axis as the compute domain
831  if (this%cart_name .eq. "X" .or. this%cart_name .eq. "Y") then
832  compute_idx(1) = 1
833  compute_idx(2) = size(this%axis_data)
834  need_to_define_axis = .true.
835  endif
836  return
837  endif
838 
839  select type(domain => this%axis_domain)
840  type is (diagdomain2d_t)
841  if (present(tile_number)) then
842  !< If the tile number is present and the current PE is not on the tile, then there is no need
843  !! to define the axis
844  if (any(mpp_get_tile_id(domain%Domain2) .ne. tile_number)) then
845  need_to_define_axis = .false.
846  return
847  endif
848  endif
849 
850  !< Get the compute domain for the current PE if it is an "X" or "Y" axis
851  select case (this%cart_name)
852  case ("X")
853  call mpp_get_compute_domain(domain%Domain2, xbegin=compute_idx(1), xend=compute_idx(2), &
854  & position=this%domain_position)
855  need_to_define_axis = .true.
856  case ("Y")
857  call mpp_get_compute_domain(domain%Domain2, ybegin=compute_idx(1), yend=compute_idx(2), &
858  & position=this%domain_position)
859  need_to_define_axis = .true.
860  end select
861  end select
862 
863  end subroutine get_compute_domain
864 
865  !!!!!!!!!!!!!!!!!! SUB AXIS PROCEDURES !!!!!!!!!!!!!!!!!
866  !> @brief Fills in the information needed to define a subaxis
867  subroutine fill_subaxis(this, starting_index, ending_index, axis_id, parent_id, parent_axis_name, compute_idx, &
868  global_idx, zbounds, nz_subaxis)
869  class(fmsdiagsubaxis_type) , INTENT(INOUT) :: this !< diag_sub_axis obj
870  integer , intent(in) :: starting_index !< Starting index of the subRegion for the PE
871  integer , intent(in) :: ending_index !< Ending index of the subRegion for the PE
872  integer , intent(in) :: axis_id !< Axis id to assign to the subaxis
873  integer , intent(in) :: parent_id !< The id of the parent axis the subaxis belongs to
874  character(len=*) , intent(in) :: parent_axis_name !< Name of the parent_axis
875  integer , intent(in) :: compute_idx(2) !< Starting and ending index of
876  !! the axis's compute domain
877  integer, optional, intent(in) :: global_idx(2) !< Starting and ending index of
878  !! the axis's compute domain
879  real(kind=r4_kind), optional, intent(in) :: zbounds(2) !< Bounds of the z-axis
880  integer, optional, intent(in) :: nz_subaxis !< The number of z subaxis that have been defined
881  !! in the file
882 
883  integer :: nsubaxis !< The subaxis number in the axis name subXX
884  character(len=2) :: nsubaxis_char !< nsubaxis converted to a string
885 
886  nsubaxis = 1
887  if (present(nz_subaxis)) nsubaxis = nz_subaxis
888 
889  this%axis_id = axis_id
890 
891  ! The min and max were added here to support axis that are both increasing and decreasing
892  this%starting_index = min(starting_index, ending_index)
893  this%ending_index = max(starting_index, ending_index)
894  this%parent_axis_id = parent_id
895  write(nsubaxis_char, '(i2.2)') nsubaxis
896  this%subaxis_name = trim(parent_axis_name)//"_sub"//nsubaxis_char
897  this%compute_idx = compute_idx
898 
899  if (present(zbounds)) then
900  ! This is needed to avoid duplicating z sub axis!
901  allocate(this%zbounds(2))
902  this%zbounds = zbounds
903  endif
904 
905  if (present(global_idx)) then
906  ! This is needed for the "domain_decomposition" attribute which is needed for the combiner
907  allocate(this%global_idx(2))
908  this%global_idx = global_idx
909  endif
910  end subroutine fill_subaxis
911 
912  !> @brief Get the axis length of a subaxis
913  !> @return the axis length
914  function axis_length(this) &
915  result(res)
916  class(fmsdiagsubaxis_type) , INTENT(IN) :: this !< diag_sub_axis obj
917  integer :: res
918 
919  res = this%ending_index - this%starting_index + 1
920  end function
921 
922  !> @brief Accesses its member starting_index
923  !! @return a copy of the starting_index
924  function get_starting_index(this) result(indx)
925  class(fmsdiagsubaxis_type), intent(in) :: this !< diag_sub_axis object
926  integer :: indx !< Result to return
927  indx = this%starting_index
928  end function get_starting_index
929 
930  !> @brief Accesses its member ending_index
931  !! @return a copy of the ending_index
932  function get_ending_index(this) result(indx)
933  class(fmsdiagsubaxis_type), intent(in) :: this !< diag_sub_axis object
934  integer :: indx !< Result to return
935  indx = this%ending_index
936  end function get_ending_index
937 
938  !> @brief Accesses its member compute_indices
939  !! @return a copy of the ending_index
940  function get_compute_indices(this) result(indx)
941  class(fmsdiagsubaxis_type), intent(in) :: this !< diag_sub_axis object
942  integer :: indx(2) !< Result to return
943  indx = this%compute_idx
944  end function get_compute_indices
945 
946  !> @brief Determines if the zbounds passed in are the same as those in the file
947  !! @return .True. if the zbounds are the same
948  function is_same_zbounds(this, zbounds) result(is_same)
949  class(fmsdiagsubaxis_type), intent(in) :: this !< diag_sub_axis object
950  real(kind=r4_kind), intent(in) :: zbounds(2) !< zbounds to compare with
951  logical :: is_same
952 
953  is_same = zbounds(1) .eq. this%zbounds(1) .and. zbounds(2) .eq. this%zbounds(2)
954  end function
955 
956  !> @brief Get the ntiles in a domain
957  !> @return the number of tiles in a domain
958  function get_ntiles(this) &
959  result(ntiles)
960  class(diagdomain_t), INTENT(IN) :: this !< diag_axis obj
961 
962  integer :: ntiles
963 
964  select type (this)
965  type is (diagdomain2d_t)
966  ntiles = mpp_get_ntile_count(this%domain2)
967  end select
968  end function get_ntiles
969 
970  !> @brief Get the length of a 2D domain
971  !> @return Length of the 2D domain
972  function get_length(this, cart_axis, domain_position, global_length) &
973  result(length)
974  class(diagdomain_t), INTENT(IN) :: this !< diag_axis obj
975  character(len=*), INTENT(IN) :: cart_axis !< cart_axis of the axis
976  integer, INTENT(IN) :: domain_position !< Domain position (CENTER, NORTH, EAST)
977  integer, INTENT(IN) :: global_length !< global_length of the axis
978 
979  integer :: length
980 
981  select type (this)
982  type is(diagdomain2d_t)
983  if (trim(cart_axis) == "X") call mpp_get_compute_domain(this%Domain2, xsize=length, position=domain_position)
984  if (trim(cart_axis) == "Y") call mpp_get_compute_domain(this%Domain2, ysize=length, position=domain_position)
985  class default
986  !< If domain is 1D or UG, just set it to the global length
987  length = global_length
988  end select
989  end function get_length
990 
991  !!!!!!!!!!!!!!!!! FMS_DOMAIN PROCEDURES !!!!!!!!!!!!!!!!!
992 
993  !> @brief Set the axis domain
994  subroutine set_axis_domain(this, Domain, Domain2, DomainU)
995  class(diagdomain_t) :: this !< fms_domain obj
996  TYPE(domain1d), INTENT(in), OPTIONAL :: Domain !< 1d domain
997  TYPE(domain2d), INTENT(in), OPTIONAL :: Domain2 !< 2d domain
998  TYPE(domainug), INTENT(in), OPTIONAL :: DomainU !< Unstructured domain
999 
1000  select type(this)
1001  type is (diagdomain1d_t)
1002  this%Domain = domain
1003  type is (diagdomain2d_t)
1004  this%Domain2 = domain2
1005  type is (diagdomainug_t)
1006  this%DomainUG = domainu
1007  end select
1008  end subroutine set_axis_domain
1009 
1010  !< @brief Allocates the array of axis/subaxis objects
1011  !! @return true if there the aray of axis/subaxis objects is allocated
1012  logical function fms_diag_axis_object_init(axis_array)
1013  class(fmsdiagaxiscontainer_type) , allocatable, intent(inout) :: axis_array(:) !< Array of diag_axis
1014 
1015  if (allocated(axis_array)) call mpp_error(fatal, "The diag_axis containers is already allocated")
1016  allocate(axis_array(max_axes))
1017  !axis_array%axis_id = DIAG_NULL
1018 
1019  fms_diag_axis_object_init = .true.
1020  end function fms_diag_axis_object_init
1021 
1022  !< @brief Deallocates the array of axis/subaxis objects
1023  !! @return false if the aray of axis/subaxis objects was allocated
1024  logical function fms_diag_axis_object_end(axis_array)
1025  class(fmsdiagaxiscontainer_type) , allocatable, intent(inout) :: axis_array(:) !< Array of diag_axis
1026 
1027  if (allocated(axis_array)) deallocate(axis_array)
1028  fms_diag_axis_object_end = .false.
1029 
1030  end function fms_diag_axis_object_end
1031 
1032  !< @brief Determine the axis name of an axis_object
1033  !! @return The name of the axis
1034  !! @note This function may be called from the field object (i.e. to determine the dimension names for io),
1035  !! The field object only contains the parent axis ids, because the subregion is defined in a per file basis,
1036  !! so the is_regional flag is needed so that the correct axis name can be used
1037  pure function get_axis_name(this, is_regional) &
1038  result(axis_name)
1039  class(fmsdiagaxis_type), intent(in) :: this !< Axis object
1040  logical, intent(in), optional :: is_regional !< Flag indicating if the axis is regional
1041 
1042  character(len=:), allocatable :: axis_name
1043 
1044  select type (this)
1045  type is (fmsdiagfullaxis_type)
1046  axis_name = this%axis_name
1047  if (present(is_regional)) then
1048  if (is_regional) then
1049  if (this%cart_name .eq. "X" .or. this%cart_name .eq. "Y") axis_name = axis_name//"_sub01"
1050  endif
1051  endif
1052  type is (fmsdiagsubaxis_type)
1053  axis_name = this%subaxis_name
1054  end select
1055  end function get_axis_name
1056 
1057  !< @brief Determine if the axis is a Z axis by looking at the cartesian name
1058  !! @return .True. if the axis is a Z axis
1059  pure logical function is_z_axis(this)
1060  class(fmsdiagaxis_type), intent(in) :: this !< Axis object
1061  is_z_axis = .false.
1062  select type (this)
1063  type is (fmsdiagfullaxis_type)
1064  if (this%cart_name .eq. "Z") is_z_axis = .true.
1065  end select
1066  end function
1067 
1068  !> @brief Check if a cart_name is valid and crashes if it isn't
1069  subroutine check_if_valid_cart_name(cart_name)
1070  character(len=*), intent(in) :: cart_name
1071 
1072  select case (cart_name)
1073  case ("X", "Y", "Z", "T", "U", "N")
1074  case default
1075  call mpp_error(fatal, "diag_axit_init: Invalid cart_name: "//cart_name//&
1076  "The acceptable values are X, Y, Z, T, U, N.")
1077  end select
1078  end subroutine check_if_valid_cart_name
1079 
1080  !> @brief Check if a domain_position is valid and crashes if it isn't
1081  subroutine check_if_valid_domain_position(domain_position)
1082  integer, INTENT(IN) :: domain_position
1083 
1084  select case (domain_position)
1085  case (center, north, east)
1086  case default
1087  call mpp_error(fatal, "diag_axit_init: Invalid domain_positon. &
1088  &The acceptable values are NORTH, EAST, CENTER")
1089  end select
1090  end subroutine check_if_valid_domain_position
1091 
1092  !> @brief Check if a direction is valid and crashes if it isn't
1093  subroutine check_if_valid_direction(direction)
1094  integer, INTENT(IN) :: direction
1095 
1096  select case(direction)
1097  case(-1, 0, 1)
1098  case default
1099  call mpp_error(fatal, "diag_axit_init: Invalid direction. &
1100  &The acceptable values are-1 0 1")
1101  end select
1102  end subroutine check_if_valid_direction
1103 
1104  !> @brief Loop through a variable's axis_id to determine and return the domain type and domain to use
1105  subroutine get_domain_and_domain_type(diag_axis, axis_id, domain_type, domain, var_name)
1106  class(fmsdiagaxiscontainer_type), target, intent(in) :: diag_axis(:) !< Array of diag_axis
1107  integer, INTENT(IN) :: axis_id(:) !< Array of axis ids
1108  integer, INTENT(OUT) :: domain_type !< fileobj_type to use
1109  CLASS(diagdomain_t), POINTER, INTENT(OUT) :: domain !< Domain
1110  character(len=*), INTENT(IN) :: var_name !< Name of the variable (for error messages)
1111 
1112  integer :: i !< For do loops
1113  integer :: j !< axis_id(i) (for less typing)
1114 
1115  domain_type = no_domain
1116  domain => null()
1117 
1118  do i = 1, size(axis_id)
1119  j = axis_id(i)
1120  select type (axis => diag_axis(j)%axis)
1121  type is (fmsdiagfullaxis_type)
1122  !< Check that all the axis are in the same domain
1123  if (domain_type .ne. axis%type_of_domain) then
1124  !< If they are different domains, one of them can be NO_DOMAIN
1125  !! i.e a variable can have axis that are domain decomposed (x,y) and an axis that isn't (z)
1126  if (domain_type .eq. no_domain .or. axis%type_of_domain .eq. no_domain ) then
1127  !< Update the domain_type and domain, if needed
1128  if ((axis%type_of_domain .eq. two_d_domain .and. size(axis_id) > 1) &
1129  & .or. axis%type_of_domain .eq. ug_domain) then
1130  domain_type = axis%type_of_domain
1131  domain => axis%axis_domain
1132  endif
1133  else
1134  call mpp_error(fatal, "The variable:"//trim(var_name)//" has axis that are not in the same domain")
1135  endif
1136  endif
1137  end select
1138  enddo
1139  end subroutine get_domain_and_domain_type
1140 
1141  !> @brief Fill in the subaxis object for a subRegion defined by index
1142  subroutine define_new_subaxis_index(parent_axis, subRegion, diag_axis, naxis, is_x_or_y, write_on_this_pe)
1143  class(fmsdiagaxiscontainer_type), target, intent(inout) :: diag_axis(:) !< Diag_axis object
1144  type(fmsdiagfullaxis_type), intent(inout) :: parent_axis !< axis object of the parent
1145  integer, intent(inout) :: naxis !< Number of axis registered
1146  type(subregion_type), intent(in) :: subregion !< SubRegion definition from the yaml
1147  integer, intent(in) :: is_x_or_y !< Flag indicating if it is
1148  !! a x or y axis
1149  logical, intent(out) :: write_on_this_pe !< .true. if the subregion
1150  !! is on this PE
1151  integer :: compute_idx(2) !< Indices of the compute domain
1152  integer :: global_idx(2) !< Indices of the "global" domain
1153  integer :: starting_index !< starting index of the subregion
1154  integer :: ending_index !< ending index of the subregion
1155 
1156  call parent_axis%get_compute_domain(compute_idx, write_on_this_pe, tile_number=subregion%tile)
1157  if (.not. write_on_this_pe) return
1158 
1159  !< Determine if the PE's compute domain is inside the subRegion
1160  !! If it is get the starting and ending indices for that PE
1161  call parent_axis%get_indices(compute_idx, subregion%corners(:,is_x_or_y), starting_index, ending_index, &
1162  write_on_this_pe)
1163 
1164  if (.not. write_on_this_pe) return
1165 
1166  select type(corners=> subregion%corners)
1167  type is (integer(kind=i4_kind))
1168  global_idx(1) = minval(corners(:,is_x_or_y))
1169  global_idx(2) = maxval(corners(:,is_x_or_y))
1170  end select
1171 
1172  !< If it made it to this point, the current PE is in the subRegion!
1173  call define_new_axis(diag_axis, parent_axis, naxis, parent_axis%axis_id, &
1174  starting_index, ending_index, compute_idx, global_idx)
1175 
1176  end subroutine define_new_subaxis_index
1177 
1178  !> @brief Fill in the subaxis object for a subRegion defined by lat lon
1179  subroutine define_new_subaxis_latlon(diag_axis, axis_ids, naxis, subRegion, is_cube_sphere, write_on_this_pe)
1180  class(fmsdiagaxiscontainer_type), target, intent(inout) :: diag_axis(:) !< Diag_axis object
1181  integer, INTENT(in) :: axis_ids(:) !< Array of axes_ids
1182  integer, intent(inout) :: naxis !< Number of axis registered
1183  type(subregion_type), intent(in) :: subregion !< SubRegion definition from the yaml
1184  logical, intent(in) :: is_cube_sphere !< .true. if this is a cubesphere
1185  logical, intent(out) :: write_on_this_pe !< .true. if the subregion
1186  !! is on this PE
1187 
1188  real :: lat(2) !< Starting and ending lattiude of the subRegion
1189  real :: lon(2) !< Starting and ending longitude or the subRegion
1190  integer :: lat_indices(2) !< Starting and ending latitude indices of the subRegion
1191  integer :: lon_indices(2) !< Starting and ending longitude indices of the subRegion
1192  integer :: compute_idx(2) !< Compute domain of the current axis
1193  integer :: starting_index(2) !< Starting index of the subRegion for the current PE for the "x" and "y"
1194  !! direction
1195  integer :: ending_index(2) !< Ending index of the subRegion for the current PE for the "x" and "y" direction
1196  logical :: need_to_define_axis(2) !< .true. if it is needed to define the subaxis for the "x" and "y" direction
1197  integer :: i !< For do loops
1198  integer :: parent_axis_ids(2) !< The axis id of the parent axis for the "x" and "y" direction
1199  logical :: is_x_y_axis !< .true. if the axis is x or y
1200  integer :: compute_idx_2(2, 2) !< Starting and ending indices of the compute domain for the "x" and "y" direction
1201  integer :: global_idx (2, 2) !< Starting and ending indices of the global domain for the "x" and "y" direction
1202 
1203  write_on_this_pe = .false.
1204  need_to_define_axis = .true.
1205  parent_axis_ids = diag_null
1206 
1207  !< Get the rectangular coordinates of the subRegion
1208  !! If the subRegion is not rectangular, the points outside of the subRegion will be masked
1209  !! out later
1210  select type (corners => subregion%corners)
1211  type is (real(kind=r4_kind))
1212  lon(1) = minval(corners(:,1))
1213  lon(2) = maxval(corners(:,1))
1214  lat(1) = minval(corners(:,2))
1215  lat(2) = maxval(corners(:,2))
1216  end select
1217 
1218  if_is_cube_sphere: if (is_cube_sphere) then
1219  !< Get the starting and ending indices of the subregion in the cubesphere relative to the global domain
1220  call get_local_indices_cubesphere(lat(1), lat(2), lon(1), lon(2),&
1221  & lon_indices(1), lon_indices(2), lat_indices(1), lat_indices(2))
1222  loop_over_axis_ids: do i = 1, size(axis_ids)
1223  select_axis_type: select type (parent_axis => diag_axis(axis_ids(i))%axis)
1224  type is (fmsdiagfullaxis_type)
1225  !< Get the PEs compute domain
1226  call parent_axis%get_compute_domain(compute_idx, is_x_y_axis)
1227 
1228  !< If this is not a "X" or "Y" axis go to the next axis
1229  if (.not. is_x_y_axis) cycle
1230 
1231  !< Determine if the PE's compute domain is inside the subRegion
1232  !! If it is get the starting and ending indices for that PE
1233  if (parent_axis%cart_name .eq. "X") then
1234  call parent_axis%get_indices(compute_idx, lon_indices, starting_index(1), ending_index(1), &
1235  need_to_define_axis(1))
1236  parent_axis_ids(1) = axis_ids(i)
1237  compute_idx_2(1,:) = compute_idx
1238  global_idx(1,:) = lon_indices
1239  else if (parent_axis%cart_name .eq. "Y") then
1240  call parent_axis%get_indices(compute_idx, lat_indices, starting_index(2), ending_index(2), &
1241  need_to_define_axis(2))
1242  parent_axis_ids(2) = axis_ids(i)
1243  compute_idx_2(2,:) = compute_idx
1244  global_idx(2,:) = lat_indices
1245  endif
1246  end select select_axis_type
1247  enddo loop_over_axis_ids
1248  else if_is_cube_sphere
1249  loop_over_axis_ids2: do i = 1, size(axis_ids)
1250  select type (parent_axis => diag_axis(axis_ids(i))%axis)
1251  type is (fmsdiagfullaxis_type)
1252  !< Get the PEs compute domain
1253  call parent_axis%get_compute_domain(compute_idx, is_x_y_axis)
1254 
1255  !< If this is not a "X" or "Y" axis go to the next axis
1256  if (.not. is_x_y_axis) cycle
1257 
1258  !< Get the starting and ending indices of the subregion relative to the global grid
1259  if (parent_axis%cart_name .eq. "X") then
1260  select type(adata=>parent_axis%axis_data)
1261  type is (real(kind=r8_kind))
1262  lon_indices(1) = nearest_index(real(lon(1), kind=r8_kind), adata)
1263  lon_indices(2) = nearest_index(real(lon(2), kind=r8_kind), adata)
1264  type is (real(kind=r4_kind))
1265  lon_indices(1) = nearest_index(real(lon(1), kind=r4_kind), adata)
1266  lon_indices(2) = nearest_index(real(lon(2), kind=r4_kind), adata)
1267  end select
1268  call parent_axis%get_indices(compute_idx, lon_indices, starting_index(1), ending_index(1), &
1269  need_to_define_axis(1))
1270  parent_axis_ids(1) = axis_ids(i)
1271  compute_idx_2(1,:) = compute_idx
1272  global_idx(1,:) = lon_indices
1273  else if (parent_axis%cart_name .eq. "Y") then
1274  select type(adata=>parent_axis%axis_data)
1275  type is (real(kind=r8_kind))
1276  lat_indices(1) = nearest_index(real(lat(1), kind=r8_kind), adata)
1277  lat_indices(2) = nearest_index(real(lat(2), kind=r8_kind), adata)
1278  type is (real(kind=r4_kind))
1279  lat_indices(1) = nearest_index(real(lat(1), kind=r4_kind), adata)
1280  lat_indices(2) = nearest_index(real(lat(2), kind=r4_kind), adata)
1281  end select
1282  call parent_axis%get_indices(compute_idx, lat_indices, starting_index(2), ending_index(2), &
1283  need_to_define_axis(2))
1284  parent_axis_ids(2) = axis_ids(i)
1285  compute_idx_2(2,:) = compute_idx
1286  global_idx(2,:) = lat_indices
1287  endif
1288  end select
1289  enddo loop_over_axis_ids2
1290  endif if_is_cube_sphere
1291 
1292  !< If the PE's compute is not inside the subRegion move to the next axis
1293  if (any(.not. need_to_define_axis )) return
1294 
1295  !< If it made it to this point, the current PE is in the subRegion!
1296  write_on_this_pe = .true.
1297 
1298  do i = 1, size(parent_axis_ids)
1299  if (parent_axis_ids(i) .eq. diag_null) cycle
1300  select type (parent_axis => diag_axis(parent_axis_ids(i))%axis)
1301  type is (fmsdiagfullaxis_type)
1302  call define_new_axis(diag_axis, parent_axis, naxis, parent_axis_ids(i), &
1303  starting_index(i), ending_index(i), compute_idx_2(i,:), global_idx(i,:))
1304  end select
1305  enddo
1306 
1307  end subroutine define_new_subaxis_latlon
1308 
1309  !> @brief Creates a new subaxis and fills it will all the information it needs
1310  subroutine define_new_axis(diag_axis, parent_axis, naxis, parent_id, &
1311  starting_index, ending_index, compute_idx, global_idx, new_axis_id, zbounds, &
1312  nz_subaxis)
1313 
1314  class(fmsdiagaxiscontainer_type), target, intent(inout) :: diag_axis(:) !< Diag_axis object
1315  class(fmsdiagfullaxis_type), intent(inout) :: parent_axis !< The parent axis
1316  integer, intent(inout) :: naxis !< The number of axis that
1317  !! have been defined
1318  integer, intent(in) :: parent_id !< Id of the parent axis
1319  integer, intent(in) :: starting_index !< PE's Starting index
1320  integer, intent(in) :: ending_index !< PE's Ending index
1321  integer, intent(in) :: compute_idx(2) !< Starting and ending index of
1322  !! the axis's compute domain
1323  integer, optional, intent(in) :: global_idx(2) !< Starting and ending index of
1324  !! the axis's global domain
1325  integer, optional, intent(out) :: new_axis_id !< Axis id of the axis this is creating
1326  real(kind=r4_kind), optional, intent(in) :: zbounds(2) !< Bounds of the Z axis
1327  integer, optional, intent(in) :: nz_subaxis !< The number of z subaxis that have
1328  !! been defined in the file
1329 
1330  naxis = naxis + 1 !< This is the axis id of the new axis!
1331 
1332  !< Add the axis_id of the new subaxis to the parent axis
1333  parent_axis%nsubaxis = parent_axis%nsubaxis + 1
1334  parent_axis%subaxis(parent_axis%nsubaxis) = naxis
1335 
1336  !< Allocate the new axis as a subaxis and fill it
1337  allocate(fmsdiagsubaxis_type :: diag_axis(naxis)%axis)
1338  diag_axis(naxis)%axis%axis_id = naxis
1339  if (present(new_axis_id)) new_axis_id = naxis
1340 
1341  select type (sub_axis => diag_axis(naxis)%axis)
1342  type is (fmsdiagsubaxis_type)
1343  call sub_axis%fill_subaxis(starting_index, ending_index, naxis, parent_id, &
1344  parent_axis%axis_name, compute_idx, global_idx=global_idx, zbounds=zbounds, nz_subaxis=nz_subaxis)
1345  end select
1346  end subroutine define_new_axis
1347 
1348  !< @brief Determine the parent_axis_id of a subaxis
1349  !! @return parent_axis_id if it is a subaxis and diag_null if is not a subaxis
1350  pure function get_parent_axis_id(this) &
1351  result(parent_axis_id)
1352 
1353  class(fmsdiagaxis_type), intent(in) :: this !< Axis Object
1354  integer :: parent_axis_id
1355 
1356  select type (this)
1357  type is (fmsdiagfullaxis_type)
1358  parent_axis_id = diag_null
1359  type is (fmsdiagsubaxis_type)
1360  parent_axis_id = this%parent_axis_id
1361  type is (fmsdiagdiurnalaxis_type)
1362  parent_axis_id = diag_null
1363  end select
1364 
1365  end function
1366 
1367  !< @brief Determine the most recent subaxis id in a diag_axis object
1368  !! @return the most recent subaxis id in a diag_axis object
1369  pure function get_subaxes_id(this) &
1370  result(sub_axis_id)
1371 
1372  class(fmsdiagaxis_type), intent(in) :: this !< Axis Object
1373  integer :: sub_axis_id
1374 
1375  sub_axis_id = this%axis_id
1376  select type (this)
1377  type is (fmsdiagfullaxis_type)
1378  if (this%cart_name .ne. "Z") sub_axis_id = this%subaxis(this%nsubaxis)
1379  end select
1380 
1381  end function
1382 
1383  !< @brief Parses the "compress" attribute to get the names of the two axis
1384  !! @return the names of the structured axis
1385  pure function parse_compress_att(compress_att) &
1386  result(axis_names)
1387  class(*), intent(in) :: compress_att(:) !< The compress attribute to parse
1388  character(len=120) :: axis_names(2)
1389 
1390  integer :: ios !< Errorcode after parsing the compress attribute
1391 
1392  select type (compress_att)
1393  type is (character(len=*))
1394  read(compress_att(1),*, iostat=ios) axis_names
1395  if (ios .ne. 0) axis_names = ""
1396  class default
1397  axis_names = ""
1398  end select
1399  end function parse_compress_att
1400 
1401  !< @brief Determine the axis id of a axis
1402  !! @return Axis id
1403  pure function get_axis_id_from_name(axis_name, diag_axis, naxis, set_name) &
1404  result(axis_id)
1405  class(fmsdiagaxiscontainer_type), intent(in) :: diag_axis(:) !< Array of axis object
1406  character(len=*), intent(in) :: axis_name !< Name of the axis
1407  integer, intent(in) :: naxis !< Number of axis that have been registered
1408  character(len=*), intent(in) :: set_name !< Name of the axis set
1409  integer :: axis_id
1410 
1411  integer :: i !< For do loops
1412 
1413  axis_id = diag_null
1414  do i = 1, naxis
1415  select type(axis => diag_axis(i)%axis)
1416  type is (fmsdiagfullaxis_type)
1417  if (trim(axis%axis_name) .eq. trim(axis_name)) then
1418  if (trim(axis%set_name) .eq. trim(set_name)) then
1419  axis_id = i
1420  return
1421  endif
1422  endif
1423  end select
1424  enddo
1425 
1426  end function get_axis_id_from_name
1427 
1428  !< @brief Get the number of diurnal samples for a diurnal axis
1429  !! @return The number of diurnal samples
1430  pure function get_diurnal_axis_samples(this) &
1431  result(n_diurnal_samples)
1432 
1433  class(fmsdiagdiurnalaxis_type), intent(in) :: this !< Axis Object
1434  integer :: n_diurnal_samples
1435 
1436  n_diurnal_samples = this%ndiurnal_samples
1437  end function get_diurnal_axis_samples
1438 
1439  !< @brief Writes out the metadata for a diurnal axis
1440  subroutine write_diurnal_metadata(this, fms2io_fileobj)
1441  class(fmsdiagdiurnalaxis_type), intent(in) :: this !< Diurnal axis Object
1442  class(fmsnetcdffile_t), intent(inout) :: fms2io_fileobj !< Fms2_io fileobj to write the data to
1443 
1444  call register_axis(fms2io_fileobj, this%axis_name, size(this%diurnal_data))
1445  call register_field(fms2io_fileobj, this%axis_name, pack_size_str, (/trim(this%axis_name)/))
1446  call register_variable_attribute(fms2io_fileobj, this%axis_name, "units", &
1447  &trim(this%units), str_len=len_trim(this%units))
1448  call register_variable_attribute(fms2io_fileobj, this%axis_name, "long_name", &
1449  &trim(this%long_name), str_len=len_trim(this%long_name))
1450  if (this%edges_id .ne. diag_null) &
1451  call register_variable_attribute(fms2io_fileobj, this%axis_name, "edges", &
1452  &trim(this%edges_name), str_len=len_trim(this%edges_name))
1453  end subroutine write_diurnal_metadata
1454 
1455  !> @brief Creates a new z subaxis to use
1456  subroutine create_new_z_subaxis(zbounds, var_axis_ids, diag_axis, naxis, file_axis_id, nfile_axis, nz_subaxis, &
1457  error_mseg)
1458  real(kind=r4_kind), intent(in) :: zbounds(2) !< Bounds of the Z axis
1459  integer, intent(inout) :: var_axis_ids(:) !< The variable's axis_ids
1460  class(fmsdiagaxiscontainer_type), target, intent(inout) :: diag_axis(:) !< Array of diag_axis objects
1461  integer, intent(inout) :: naxis !< Number of axis that have been
1462  !! registered
1463  integer, intent(inout) :: file_axis_id(:) !< The file's axis_ids
1464  integer, intent(inout) :: nfile_axis !< Number of axis that have been
1465  !! defined in file
1466  integer, intent(inout) :: nz_subaxis !< The number of z subaxis currently
1467  !! defined in the file
1468  character(len=*), intent(inout) :: error_mseg !! Message to include in error message
1469  !! if there is an error
1470 
1471  class(*), pointer :: zaxis_data(:) !< The data of the full zaxis
1472  integer :: subaxis_indices(2) !< The starting and ending indices of the subaxis relative to the full
1473  !! axis
1474  integer :: i !< For do loops
1475  integer :: subaxis_id !< The id of the new z subaxis
1476  integer :: parent_axis_id !< Id of parent axis id
1477  integer :: zaxis_index !< Index of the z axis (i.e 3 if the variable is x,y,z)
1478  type(fmsdiagfullaxis_type), pointer :: parent_axis !< Pointer to the parent axis
1479 
1480  parent_axis_id = diag_null
1481  zaxis_index = diag_null
1482 
1483  !< Determine which axis is the z axis:
1484  do i = 1, size(var_axis_ids)
1485  select type (parent_axis => diag_axis(var_axis_ids(i))%axis)
1486  type is (fmsdiagfullaxis_type)
1487  if (parent_axis%cart_name .eq. "Z") then
1488  parent_axis_id = var_axis_ids(i)
1489  zaxis_index = i
1490  endif
1491  end select
1492  enddo
1493 
1494  if (parent_axis_id .eq. diag_null) then
1495  call mpp_error(fatal, "create_new_z_subaxis:: unable to find the zaxis for "//trim(error_mseg))
1496  endif
1497 
1498  !< Determine if the axis was already created
1499  do i = 1, nfile_axis
1500  select type (axis => diag_axis(file_axis_id(i))%axis)
1501  type is (fmsdiagsubaxis_type)
1502  if (axis%parent_axis_id .ne. parent_axis_id) cycle
1503  if (axis%zbounds(1) .eq. zbounds(1) .and. axis%zbounds(2) .eq. zbounds(2)) then
1504  var_axis_ids(zaxis_index) = file_axis_id(i)
1505  return
1506  endif
1507  end select
1508  enddo
1509 
1510  select type (axis => diag_axis(parent_axis_id)%axis)
1511  type is (fmsdiagfullaxis_type)
1512  zaxis_data => axis%axis_data
1513  parent_axis => axis
1514  end select
1515 
1516  select type(zaxis_data)
1517  type is (real(kind=r4_kind))
1518  !TODO need to include the conversion to "real" because nearest_index doesn't take r4s and r8s
1519  subaxis_indices(1) = nearest_index(real(zbounds(1)), real(zaxis_data))
1520  subaxis_indices(2) = nearest_index(real(zbounds(2)), real(zaxis_data))
1521  type is (real(kind=r8_kind))
1522  subaxis_indices(1) = nearest_index(real(zbounds(1)), real(zaxis_data))
1523  subaxis_indices(2) = nearest_index(real(zbounds(2)), real(zaxis_data))
1524  end select
1525 
1526  nz_subaxis = nz_subaxis + 1
1527  call define_new_axis(diag_axis, parent_axis, naxis, parent_axis%axis_id, &
1528  &subaxis_indices(1), subaxis_indices(2), (/lbound(zaxis_data,1), ubound(zaxis_data,1)/), &
1529  &new_axis_id=subaxis_id, zbounds=zbounds, nz_subaxis=nz_subaxis)
1530  var_axis_ids(zaxis_index) = subaxis_id
1531 
1532  end subroutine
1533 
1534  !> @brief Determine if the diag_axis(parent_axis_id) is the parent of diag_axis(axis_id)
1535  !! @return .True. if diag_axis(parent_axis_id) is the parent of diag_axis(axis_id)
1536  function is_parent_axis(axis_id, parent_axis_id, diag_axis) &
1537  result(rslt)
1538  integer, intent(in) :: axis_id !< Axis id to check
1539  integer, intent(in) :: parent_axis_id !< Axis id of the parent to check
1540  class(fmsdiagaxiscontainer_type), target, intent(in) :: diag_axis(:) !< Array of diag_axis objects
1541 
1542  logical :: rslt
1543 
1544  rslt = .false.
1545  select type(axis => diag_axis(axis_id)%axis)
1546  type is (fmsdiagsubaxis_type)
1547  if (axis%parent_axis_id .eq. parent_axis_id) rslt = .true.
1548  end select
1549  end function is_parent_axis
1550 
1551  !> @brief Determine the name of the z subaxis by matching the parent axis id and the zbounds
1552  !! in the diag table yaml
1553  subroutine find_z_sub_axis_name(dim_name, parent_axis_id, file_axis_id, field_yaml, diag_axis)
1554  character(len=*), intent(inout) :: dim_name !< Name of z subaxis
1555  integer, intent(in) :: parent_axis_id !< Axis id of the parent
1556  integer, intent(in) :: file_axis_id(:) !< Axis ids of the file
1557  type(diagyamlfilesvar_type), intent(in) :: field_yaml !< Field info from diag_table yaml
1558  class(fmsdiagaxiscontainer_type),intent(in) :: diag_axis(:) !< Array of axis objections
1559 
1560  integer :: id
1561  integer :: i
1562 
1563  do i = 1, size(file_axis_id)
1564  id = file_axis_id(i)
1565  select type (axis_ptr => diag_axis(id)%axis)
1566  type is (fmsdiagsubaxis_type)
1567  if (axis_ptr%parent_axis_id .eq. parent_axis_id) then
1568  if (axis_ptr%is_same_zbounds(field_yaml%get_var_zbounds())) then
1569  dim_name = axis_ptr%subaxis_name
1570  return
1571  endif
1572  endif
1573  end select
1574  enddo
1575  call mpp_error(fatal, "Unable to determine the z subaxis name for field "//&
1576  trim(field_yaml%get_var_varname())//" in file: "//&
1577  trim(field_yaml%get_var_fname()))
1578  end subroutine
1579 #endif
1580 end module fms_diag_axis_object_mod
1581 !> @}
1582 ! close documentation grouping
integer, parameter direction_down
The axis points down if positive.
Definition: diag_data.F90:106
integer function get_base_minute()
gets the module variable base_minute
Definition: diag_data.F90:549
integer function get_base_year()
gets the module variable base_year
Definition: diag_data.F90:517
integer function get_base_hour()
gets the module variable base_hour
Definition: diag_data.F90:541
integer, parameter no_domain
Use the FmsNetcdfFile_t fileobj.
Definition: diag_data.F90:100
integer max_axis_attributes
Maximum number of user definable attributes per axis.
Definition: diag_data.F90:385
character(len=6) pack_size_str
Pack size as a string to be used in fms2_io register call set to "double" or "float".
Definition: diag_data.F90:408
integer max_axes
Maximum number of independent axes.
Definition: diag_data.F90:361
integer, parameter is_x_axis
integer indicating that it is a x axis
Definition: diag_data.F90:130
integer, parameter is_y_axis
integer indicating that it is a y axis
Definition: diag_data.F90:131
integer function get_base_day()
gets the module variable base_day
Definition: diag_data.F90:533
integer, parameter ug_domain
Use the FmsNetcdfUnstructuredDomainFile_t fileobj.
Definition: diag_data.F90:102
integer, parameter direction_up
The axis points up if positive.
Definition: diag_data.F90:105
integer function get_base_month()
gets the module variable base_month
Definition: diag_data.F90:525
integer function get_base_second()
gets the module variable base_second
Definition: diag_data.F90:557
integer, parameter two_d_domain
Use the FmsNetcdfDomainFile_t fileobj.
Definition: diag_data.F90:101
Attribute type for diagnostic fields.
Definition: diag_data.F90:157
Type to hold the attributes of the field/axis/file.
Definition: diag_data.F90:334
subroutine, public get_local_indexes(latStart, latEnd, lonStart, lonEnd, istart, iend, jstart, jend)
Find the local start and local end indexes on the local PE for regional output.
Definition: diag_grid.F90:391
Adds a dimension/axis to a given netcdf file object.
Definition: fms2_io.F90:257
Defines a new field/variable within the given file. After a variable is registered,...
Definition: fms2_io.F90:282
Write data to a registered field within a file Example usage:
Definition: fms2_io.F90:334
subroutine, public define_new_subaxis_latlon(diag_axis, axis_ids, naxis, subRegion, is_cube_sphere, write_on_this_pe)
Fill in the subaxis object for a subRegion defined by lat lon.
subroutine get_dim_size_layout(this, dim_size, layout)
pure character(len=:) function, allocatable get_axis_name(this, is_regional)
integer function get_length(this, cart_axis, domain_position, global_length)
Get the length of a 2D domain.
integer function get_ending_index(this)
Accesses its member ending_index.
logical function, public fms_diag_axis_object_end(axis_array)
subroutine check_if_valid_domain_position(domain_position)
Check if a domain_position is valid and crashes if it isn't.
logical function is_x_or_y_axis(this, x_or_y)
Determine if an axis object is an x or y axis.
pure logical function is_unstructured_grid(this)
subroutine set_axis_id(this, axis_id)
Set the axis_id.
pure logical function is_z_axis(this)
integer function axis_length(this)
Get the axis length of a subaxis.
pure integer function get_diurnal_axis_samples(this)
integer function get_ntiles(this)
Get the ntiles in a domain.
subroutine, public define_new_subaxis_index(parent_axis, subRegion, diag_axis, naxis, is_x_or_y, write_on_this_pe)
Fill in the subaxis object for a subRegion defined by index.
subroutine, public find_z_sub_axis_name(dim_name, parent_axis_id, file_axis_id, field_yaml, diag_axis)
Determine the name of the z subaxis by matching the parent axis id and the zbounds in the diag table ...
pure integer function, public get_axis_id_from_name(axis_name, diag_axis, naxis, set_name)
subroutine get_compute_domain(this, compute_idx, need_to_define_axis, tile_number)
subroutine, public create_new_z_subaxis(zbounds, var_axis_ids, diag_axis, naxis, file_axis_id, nfile_axis, nz_subaxis, error_mseg)
Creates a new z subaxis to use.
subroutine, public define_diurnal_axis(diag_axis, naxis, n_diurnal_samples, is_edges)
Defined a new diurnal axis.
pure integer function, dimension(2) get_structured_axis(this)
pure logical function has_aux(this)
Determine if an axis object has an auxiliary name.
logical function is_same_zbounds(this, zbounds)
Determines if the zbounds passed in are the same as those in the file.
subroutine add_structured_axis_ids(this, axis_ids)
subroutine check_if_valid_cart_name(cart_name)
Check if a cart_name is valid and crashes if it isn't.
subroutine write_axis_metadata(this, fms2io_fileobj, edges_in_file, parent_axis)
Write the axis meta data to an open fileobj.
subroutine fill_subaxis(this, starting_index, ending_index, axis_id, parent_id, parent_axis_name, compute_idx, global_idx, zbounds, nz_subaxis)
Fills in the information needed to define a subaxis.
subroutine get_global_io_domain(this, global_io_index, use_collective_writes)
Get the starting and ending indices of the global io domain of the axis.
logical function, public fms_diag_axis_object_init(axis_array)
pure integer function get_subaxes_id(this)
integer function get_starting_index(this)
Accesses its member starting_index.
subroutine get_indices(this, compute_idx, corners_indices, starting_index, ending_index, need_to_define_axis)
Determine if the subRegion is in the current PE. If it is, determine the starting and ending indices ...
pure integer function get_parent_axis_id(this)
subroutine check_if_valid_direction(direction)
Check if a direction is valid and crashes if it isn't.
subroutine register_diag_axis_obj(this, axis_name, axis_data, units, cart_name, long_name, direction, set_name, Domain, Domain2, DomainU, aux, req, tile_count, domain_position, axis_length)
Initialize the axis.
subroutine write_axis_data(this, fms2io_fileobj, parent_axis)
Write the axis data to an open fms2io_fileobj.
subroutine, public define_new_axis(diag_axis, parent_axis, naxis, parent_id, starting_index, ending_index, compute_idx, global_idx, new_axis_id, zbounds, nz_subaxis)
Creates a new subaxis and fills it will all the information it needs.
integer function get_axis_length(this)
Get the length of the axis.
pure character(len=:) function, allocatable get_set_name(this)
Get the set name of an axis object.
pure character(len=120) function, dimension(2), public parse_compress_att(compress_att)
subroutine set_edges(this, edges_name, edges_id)
Set the name and ids of the edges.
logical function, public is_parent_axis(axis_id, parent_axis_id, diag_axis)
Determine if the diag_axis(parent_axis_id) is the parent of diag_axis(axis_id)
subroutine add_axis_attribute(this, att_name, att_value)
Add an attribute to an axis.
subroutine write_diurnal_metadata(this, fms2io_fileobj)
pure logical function has_set_name(this)
Determine if an axis object has a set_name.
pure integer function get_edges_id(this)
integer function, dimension(2) get_compute_indices(this)
Accesses its member compute_indices.
subroutine, public get_domain_and_domain_type(diag_axis, axis_id, domain_type, domain, var_name)
Loop through a variable's axis_id to determine and return the domain type and domain to use.
subroutine set_axis_domain(this, Domain, Domain2, DomainU)
Set the axis domain.
pure character(len=:) function, allocatable get_aux(this)
Get the auxiliary name of an axis object.
integer function, dimension(size(domain%tile_id(:))) mpp_get_tile_id(domain)
Returns the tile_id on current pe.
integer function mpp_get_ntile_count(domain)
Returns number of tiles in mosaic.
type(domain2d) function, pointer mpp_get_io_domain(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....
Retrieve layout associated with a domain decomposition The 1D version of this call returns the number...
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.
integer function stdout()
This function returns the current standard fortran unit numbers for output.
Definition: mpp_util.inc:42
integer function mpp_pe()
Returns processor ID.
Definition: mpp_util.inc:406
Error handler.
Definition: mpp.F90:385
Type to hold the domain info for an axis This type was created to avoid having to send in "Domain",...
Type to hold the unstructured domain.
Type to hold the diagnostic axis description.
Type to hold the diag_axis (either subaxis or a full axis)
Type to hold the diagnostic axis description.
type to hold the info a diag_field
type to hold the sub region information about a file