FMS  2024.03
Flexible Modeling System
fm_yaml.F90
1 !***********************************************************************
2 !* GNU Lesser General Public License
3 !*
4 !* This file is part of the GFDL Flexible Modeling System (FMS).
5 !*
6 !* FMS is free software: you can redistribute it and/or modify it under
7 !* the terms of the GNU Lesser General Public License as published by
8 !* the Free Software Foundation, either version 3 of the License, or (at
9 !* your option) any later version.
10 !*
11 !* FMS is distributed in the hope that it will be useful, but WITHOUT
12 !* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 !* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 !* for more details.
15 !*
16 !* You should have received a copy of the GNU Lesser General Public
17 !* License along with FMS. If not, see <http://www.gnu.org/licenses/>.
18 !***********************************************************************
19 
20 !> @defgroup fm_yaml_mod fm_yaml_mod
21 !> @ingroup fm_yaml
22 !> @brief Reads entries from a field table yaml into a
23 !! nested object for use in the field manager.
24 !!
25 !> @author Eric Stofferahn
26 !!
27 
28 !> @file
29 !> @brief File for @ref fm_yaml_mod
30 
31 !> @addtogroup fm_yaml_mod
32 !> @{
33 module fm_yaml_mod
34 #ifdef use_yaml
35 
36 use yaml_parser_mod
37 use mpp_mod, only: mpp_error, fatal
38 implicit none
39 private
40 
41 !> @}
42 
43 public :: build_fmtable
44 
45 !> @brief This type represents a subparameter block for a given variable parameter.
46 !> This type contains the name of the associated parameter and the subparameter key/value pairs
47 !> @ingroup fm_yaml_mod
48 type, public :: fmattr_t
49  integer :: id !< block id of this var
50  character(len=:), allocatable :: paramname !< name of associated parameter
51  character(len=:), dimension(:), allocatable :: keys !< name of the attribute
52  character(len=:), dimension(:), allocatable :: values !< value of the attribute
53 end type fmattr_t
54 
55 !> @brief This type represents the entries for a given variable, e.g. dust.
56 !> This type contains the name of the variable, the block id, the key/value pairs for the
57 !> variable's parameters, and any applicable subparameters
58 !> @ingroup fm_yaml_mod
59 type, public :: fmvar_t
60  integer :: id !< block id of this var
61  character(len=:), allocatable :: name !< name of the variable
62  character(len=:), dimension(:), allocatable :: keys !< names of params
63  character(len=:), dimension(:), allocatable :: values !< values of params
64  type (fmattr_t), allocatable :: attributes(:) !< attributes in this var
65 end type fmvar_t
66 
67 !> @brief This type represents the entries for a given model, e.g. land, ocean, atmosphere.
68 !> This type contains the name of the model, the block id, and the variables within this model
69 !> @ingroup fm_yaml_mod
70 type, public :: fmmodel_t
71  integer :: id !< block id of this model
72  character(len=:), allocatable :: name !< name of the model
73  type (fmvar_t), allocatable :: variables(:) !< variables in this model
74 end type fmmodel_t
75 
76 !> @brief This type represents the entries for a specific field type, e.g. a tracer.
77 !> This type contains the name of the field type, the block id, and the models within this field type
78 !> @ingroup fm_yaml_mod
79 type, public :: fmtype_t
80  integer :: id !< block id of this type
81  character(len=:), allocatable :: name !< name of the type
82  type (fmmodel_t), allocatable :: models(:) !< models in this type
83 end type fmtype_t
84 
85 !> @brief This type contains the field types within a field table.
86 !> @ingroup fm_yaml_mod
87 type, public :: fmtable_t
88  type (fmtype_t), allocatable :: types(:) !< field types in this table
89 end type fmtable_t
90 
91 contains
92 
93 !> @addtogroup fm_yaml_mod
94 !> @{
95 
96 !> @brief Subroutine to populate an fmTable by reading a yaml file, given an optional filename.
97 subroutine build_fmtable(fmTable, filename)
98  type(fmtable_t), intent(out) :: fmtable !< the field table
99  character(len=*), intent(in), optional :: filename !< the name of the yaml file
100  integer :: yfid !< file id of the yaml file
101  integer :: ntypes !< number of field types attached to this table
102  integer :: i !< Loop counter
103 
104  if (.not. present(filename)) then
105  yfid = open_and_parse_file("field_table.yaml")
106  else
107  yfid = open_and_parse_file(trim(filename))
108  endif
109 
110  ntypes = get_num_blocks(yfid, "field_table", 0)
111  allocate(fmtable%types(ntypes))
112 
113  ! Gets the block ids for the associated types of fmTable.
114  call get_block_ids(yfid, "field_table", fmtable%types(:)%id)
115 
116  do i=1,ntypes
117  call build_fmtype(fmtable%types(i), yfid)
118  enddo
119 end subroutine build_fmtable
120 
121 !> @brief Populates an fmType, which is assumed to already have its `id` parameter set.
122 subroutine build_fmtype(fmType, yfid)
123  type(fmtype_t), intent(inout) :: fmType !< type object
124  integer, intent(in) :: yfid !< file id of the yaml file
125  integer, dimension(1) :: key_ids !< array of key ids
126  character(len=256) :: key_name !< the name of a key
127  character(len=256) :: key_value !< the value of a key
128  integer :: nmodels !< number of models attached to this type
129  integer :: i !< Loop counter
130 
131  nmodels = get_num_blocks(yfid, "modlist", fmtype%id)
132  allocate(fmtype%models(nmodels))
133 
134  ! Gets the block ids for the associated models of fmType.
135  call get_block_ids(yfid, "modlist", fmtype%models(:)%id, fmtype%id)
136 
137  if (get_nkeys(yfid, fmtype%id).ne.1) then
138  call mpp_error(fatal, "fm_yaml_mod: A single `field_type` key is expected")
139  endif
140 
141  call get_key_ids(yfid, fmtype%id, key_ids)
142  call get_key_name(yfid, key_ids(1), key_name)
143  call get_key_value(yfid, key_ids(1), key_value)
144 
145  if (trim(key_name).ne."field_type") then
146  call mpp_error(fatal, "fm_yaml_mod: A single `field_type` key is expected")
147  endif
148 
149  fmtype%name = trim(key_value)
150 
151  do i=1,nmodels
152  call build_fmmodel(fmtype%models(i), yfid)
153  enddo
154 end subroutine build_fmtype
155 
156 !> @brief Populates an fmModel, which is assumed to already have its `id` parameter set.
157 subroutine build_fmmodel(fmModel, yfid)
158  type(fmmodel_t), intent(inout) :: fmModel !< model object
159  integer, intent(in) :: yfid !< file id of the yaml file
160  integer, dimension(1) :: key_ids !< array of key ids
161  character(len=256) :: key_name !< the name of a key
162  character(len=256) :: key_value !< the value of a key
163  integer :: nvars !< number of variables attached to this model
164  integer :: i !< Loop counter
165 
166  nvars = get_num_blocks(yfid, "varlist", fmmodel%id)
167  allocate(fmmodel%variables(nvars))
168 
169  ! gets the block ids for the associated variables of fmModel.
170  call get_block_ids(yfid, "varlist", fmmodel%variables(:)%id, fmmodel%id)
171 
172  if (get_nkeys(yfid, fmmodel%id).ne.1) then
173  call mpp_error(fatal, "fm_yaml_mod: A single `model_type` key is expected")
174  endif
175 
176  call get_key_ids(yfid, fmmodel%id, key_ids)
177  call get_key_name(yfid, key_ids(1), key_name)
178  call get_key_value(yfid, key_ids(1), key_value)
179 
180  if (trim(key_name).ne."model_type") then
181  call mpp_error(fatal, "fm_yaml_mod: A single `model_type` key is expected")
182  endif
183 
184  fmmodel%name = trim(key_value)
185 
186  do i=1,nvars
187  call build_fmvar(fmmodel%variables(i), yfid)
188  enddo
189 end subroutine build_fmmodel
190 
191 !> @brief Populates an fmVar and creates any associated fmAttrs
192 subroutine build_fmvar(fmVar, yfid)
193  type(fmvar_t), intent(inout) :: fmVar !< variable object
194  integer, intent(in) :: yfid !< file id of the yaml file
195  integer :: nkeys !< number of keys defined for this var
196  integer, allocatable :: key_ids(:) !< array of key ids
197  character(len=256) :: key_name !< the name of a key
198  character(len=256) :: key_value !< the value of a key
199  integer :: nattrs !< number of attribute blocks attached to this var
200  integer :: nmethods !< total number of methods attached to this var
201  integer :: maxln !< max string length of method names
202  integer :: maxlv !< max string length of method values
203  character(:), allocatable :: attr_method_keys(:) !< Keys of methods defined in attribute blocks
204  character(:), allocatable :: attr_method_values(:) !< Values of methods defined in attribute blocks
205  integer :: i_name !< Index of the key containing the variable's name
206  integer :: i, j !< Loop indices
207 
208  ! Read attribute blocks attached to this variable
209  call fmvar_read_attrs(fmvar, yfid, attr_method_keys, attr_method_values)
210  nattrs = size(attr_method_keys)
211 
212  nkeys = get_nkeys(yfid, fmvar%id)
213  allocate(key_ids(nkeys))
214  call get_key_ids(yfid, fmvar%id, key_ids)
215 
216  maxln = len(attr_method_keys)
217  maxlv = len(attr_method_values)
218  i_name = -1
219 
220  do i=1,nkeys
221  call get_key_name(yfid, key_ids(i), key_name)
222  call get_key_value(yfid, key_ids(i), key_value)
223 
224  if (trim(key_name) .eq. "variable") then
225  if (i_name .ne. -1) then
226  call mpp_error(fatal, "fm_yaml_mod: A variable can have only one `variable` key")
227  endif
228 
229  fmvar%name = trim(key_value)
230  i_name = i
231  else
232  maxln = max(maxln, len_trim(key_name))
233  maxlv = max(maxlv, len_trim(key_value))
234  endif
235  enddo
236 
237  if (i_name .eq. -1) then
238  call mpp_error(fatal, "fm_yaml_mod: Every variable must have a `variable` key")
239  endif
240 
241  ! Number of methods is the number of keys (excluding `variable`), plus one for each attribute block.
242  nmethods = nkeys - 1 + nattrs
243 
244  allocate(character(len=maxln)::fmVar%keys(nmethods))
245  allocate(character(len=maxlv)::fmVar%values(nmethods))
246 
247  j = 1
248  do i=1,nkeys
249  if (i.eq.i_name) cycle ! Exclude `variable` key
250 
251  call get_key_name(yfid, key_ids(i), key_name)
252  call get_key_value(yfid, key_ids(i), key_value)
253  fmvar%keys(j) = trim(key_name)
254  fmvar%values(j) = trim(key_value)
255 
256  j = j + 1
257  enddo
258 
259  ! Add methods defined within attribute blocks.
260  fmvar%keys(j:) = attr_method_keys
261  fmvar%values(j:) = attr_method_values
262 end subroutine build_fmvar
263 
264 !> @brief Reads the attribute blocks attached to a variable and populates the associated fmAttr structures.
265 !! Returns two arrays containing key/value pairs of all methods defined via attribute blocks.
266 subroutine fmvar_read_attrs(fmVar, yfid, method_keys, method_values)
267  type(fmvar_t), intent(inout) :: fmVar !< variable object
268  integer, intent(in) :: yfid !< file id of the yaml file
269  character(:), allocatable, intent(out) :: method_keys(:) !< Method keys (names of attribute blocks)
270  character(:), allocatable, intent(out) :: method_values(:) !< Method values from attribute blocks
271  integer :: nattrs !< number of attribute blocks
272  integer :: nkeys !< number of keys in an attribute block
273  integer, allocatable :: key_ids(:) !< array of key ids
274  character(len=256) :: key_name !< the name of a key
275  character(len=256) :: key_value !< the value of a key
276  integer :: maxln_m !< max string length of method names
277  integer :: maxlv_m !< max string length of method values
278  integer :: maxln_a !< max string length of subparameter names
279  integer :: maxlv_a !< max string length of subparameter values
280  integer,allocatable :: name_key_id(:) !< Indices of attribute `value` keys
281  integer :: i, j, k !< Loop counters
282 
283  nattrs = get_num_unique_blocks(yfid, fmvar%id)
284  allocate(fmvar%attributes(nattrs))
285  allocate(name_key_id(nattrs))
286 
287  ! gets the block ids for the associated attributes of fmVar.
288  call get_unique_block_ids(yfid, fmvar%attributes(:)%id, fmvar%id)
289 
290  maxln_m = 0
291  maxlv_m = 0
292  name_key_id = -1
293 
294  do i=1,nattrs
295  associate(fmattr => fmvar%attributes(i))
296  call get_block_name(yfid, fmattr%id, key_value)
297  fmattr%paramname = trim(key_value)
298 
299  nkeys = get_nkeys(yfid, fmattr%id)
300  allocate(key_ids(nkeys))
301  call get_key_ids(yfid, fmattr%id, key_ids)
302 
303  maxln_a = 0
304  maxlv_a = 0
305 
306  do j=1,nkeys
307  call get_key_name(yfid, key_ids(j), key_name)
308  call get_key_value(yfid, key_ids(j), key_value)
309 
310  if (trim(key_name) .eq. "value") then
311  if (name_key_id(i) .ne. -1) then
312  call mpp_error(fatal, "fm_yaml_mod: A variable attribute block can only have one `value` key")
313  endif
314 
315  maxln_m = max(maxln_m, len(fmattr%paramname))
316  maxlv_m = max(maxlv_m, len_trim(key_value))
317 
318  name_key_id(i) = key_ids(j)
319  else
320  maxln_a = max(maxln_a, len_trim(key_name))
321  maxlv_a = max(maxlv_a, len_trim(key_value))
322  endif
323  enddo
324 
325  if (name_key_id(i) .eq. -1) then
326  call mpp_error(fatal, "fm_yaml_mod: Every variable attribute must have a `value` key")
327  endif
328 
329  allocate(character(len=maxln_a)::fmAttr%keys(nkeys - 1))
330  allocate(character(len=maxlv_a)::fmAttr%values(nkeys - 1))
331 
332  k = 1
333  do j=1,nkeys
334  if (key_ids(j).eq.name_key_id(i)) cycle
335 
336  call get_key_name(yfid, key_ids(j), key_name)
337  call get_key_value(yfid, key_ids(j), key_value)
338  fmattr%keys(k) = trim(key_name)
339  fmattr%values(k) = trim(key_value)
340 
341  k = k + 1
342  enddo
343 
344  deallocate(key_ids)
345  end associate
346  enddo
347 
348  allocate(character(len=maxln_m)::method_keys(nattrs))
349  allocate(character(len=maxlv_m)::method_values(nattrs))
350 
351  do i=1,nattrs
352  method_keys(i) = fmvar%attributes(i)%paramname
353  call get_key_value(yfid, name_key_id(i), method_values(i))
354  enddo
355 end subroutine fmvar_read_attrs
356 
357 #endif
358 end module fm_yaml_mod
359 
360 !> @}
361 ! close documentation grouping
subroutine build_fmtype(fmType, yfid)
Populates an fmType, which is assumed to already have its id parameter set.
Definition: fm_yaml.F90:123
subroutine fmvar_read_attrs(fmVar, yfid, method_keys, method_values)
Reads the attribute blocks attached to a variable and populates the associated fmAttr structures....
Definition: fm_yaml.F90:267
subroutine build_fmmodel(fmModel, yfid)
Populates an fmModel, which is assumed to already have its id parameter set.
Definition: fm_yaml.F90:158
subroutine, public build_fmtable(fmTable, filename)
Subroutine to populate an fmTable by reading a yaml file, given an optional filename.
Definition: fm_yaml.F90:98
subroutine build_fmvar(fmVar, yfid)
Populates an fmVar and creates any associated fmAttrs.
Definition: fm_yaml.F90:193
This type represents a subparameter block for a given variable parameter. This type contains the name...
Definition: fm_yaml.F90:48
This type represents the entries for a given model, e.g. land, ocean, atmosphere. This type contains ...
Definition: fm_yaml.F90:70
This type contains the field types within a field table.
Definition: fm_yaml.F90:87
This type represents the entries for a specific field type, e.g. a tracer. This type contains the nam...
Definition: fm_yaml.F90:79
This type represents the entries for a given variable, e.g. dust. This type contains the name of the ...
Definition: fm_yaml.F90:59
Error handler.
Definition: mpp.F90:382
integer function, public get_nkeys(file_id, block_id)
Gets the number of key-value pairs in a block.
subroutine, public get_key_name(file_id, key_id, key_name)
Gets the key from a file id.
integer function, public open_and_parse_file(filename)
Opens and parses a yaml file.
subroutine, public get_key_ids(file_id, block_id, key_ids)
Gets the ids of the key-value pairs in a block.
subroutine, public get_block_ids(file_id, block_name, block_ids, parent_block_id)
Gets the the ids of the blocks with block_name in the yaml file If parent_block_id is present,...
integer function, public get_num_unique_blocks(file_id, parent_block_id)
Gets the number of unique blocks.
integer function, public get_num_blocks(file_id, block_name, parent_block_id)
Determines the number of blocks with block_name in the yaml file If parent_block_id is present,...
subroutine, public get_unique_block_ids(file_id, block_ids, parent_block_id)
Gets the ids of the unique block ids.
subroutine, public get_block_name(file_id, block_id, block_name)
Gets the block name form the block id.
subroutine, public get_key_value(file_id, key_id, key_value)
Gets the value from a file id.