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