Skip to content

Commit d380d5d

Browse files
committed
Allow reading fields with a constituent dimension
1 parent e252454 commit d380d5d

29 files changed

+2478
-49
lines changed

src/data/write_init_files.py

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -739,12 +739,19 @@ def get_dimension_info(hvar):
739739
- The local variable name of the vertical dimension (or None)
740740
- True if <hvar> has one dimension which is a horizontal dimension or
741741
if <hvar> has two dimensions (horizontal and vertical)
742+
- Flag if any dimensions are number_of_ccpp_constituents and needs
743+
reading separate variables by constituent and reassembled into
744+
host model indices.
742745
"""
743746
vdim_name = None
744747
legal_dims = False
745748
fail_reason = ""
749+
has_constituent_read = False
750+
746751
dims = hvar.get_dimensions()
747752
levnm = hvar.has_vertical_dimension()
753+
has_constituent_dim = any('number_of_ccpp_constituents' in dim for dim in dims)
754+
748755
# <hvar> is only 'legal' for 2 or 3 dimensional fields (i.e., 1 or 2
749756
# dimensional variables). The second dimension must be vertical.
750757
# XXgoldyXX: If we ever need to read scalars, it would have to be
@@ -785,6 +792,19 @@ def get_dimension_info(hvar):
785792
# end if
786793
suff = "; "
787794
# end if
795+
796+
# A special case where any dimensions include number_of_ccpp_constituents,
797+
# in this case the variable needs to be suffixed by _ + constituent name
798+
# and read and reassembled separately into host model constituent indices
799+
# based on constituent name.
800+
#
801+
# In this case, override legal_dims as this case will be handled separately
802+
if has_constituent_dim:
803+
has_constituent_read = True
804+
legal_dims = True
805+
fail_reason = ""
806+
# end if
807+
788808
if legal_dims and levnm:
789809
# <hvar> should be legal, find the correct local name for the
790810
# vertical dimension
@@ -808,7 +828,8 @@ def get_dimension_info(hvar):
808828
raise ValueError(f"Vertical dimension, '{levnm}', not found")
809829
# end if
810830
# end if
811-
return vdim_name, legal_dims, fail_reason
831+
832+
return vdim_name, legal_dims, fail_reason, has_constituent_read
812833

813834
def write_phys_read_subroutine(outfile, host_dict, host_vars, host_imports,
814835
phys_check_fname_str, constituent_set,
@@ -850,7 +871,7 @@ def write_phys_read_subroutine(outfile, host_dict, host_vars, host_imports,
850871
call_string_key = f"case ('{var_stdname}')"
851872

852873
# Extract vertical level variable:
853-
levnm, call_read_field, reason = get_dimension_info(hvar)
874+
levnm, call_read_field, reason, has_constituent_read = get_dimension_info(hvar)
854875
if hvar.get_prop_value('protected'):
855876
call_read_field = False
856877
if reason:
@@ -860,20 +881,39 @@ def write_phys_read_subroutine(outfile, host_dict, host_vars, host_imports,
860881
# end if
861882
lvar = hvar.get_prop_value('local_name')
862883
reason += f"{suff}{lvar} is a protected variable"
884+
# end if
885+
863886
# Set "read_field" call string:
864887
if call_read_field:
865-
# Replace vertical dimension with local name
866-
call_str = "call read_field(file, " + \
867-
f"'{var_stdname}', input_var_names(:,name_idx), "
868-
if levnm is not None:
869-
call_str += f"'{levnm}', "
870-
# end if
871-
err_on_not_found_string = ""
872-
if var_stdname in vars_init_value:
873-
# if initial value is available, do not throw error when not found in initial condition file.
874-
err_on_not_found_string = ", error_on_not_found=.false."
888+
if has_constituent_read:
889+
# Special case for constituent-dimension variables.
890+
call_str = f"call read_constituent_dimensioned_field(const_props, file, '{var_stdname}', input_var_names(:,name_idx), "
891+
if levnm is not None:
892+
call_str += f"'{levnm}', "
893+
# end if
894+
895+
initial_value_string = ""
896+
if var_stdname in vars_init_value:
897+
# if initial value is available, pass it to the read routine
898+
# so it can be used for this variable when data for some
899+
# constituent cannot be found.
900+
initial_value_string = f", initial_value={vars_init_value[var_stdname]}_kind_phys"
901+
# end if
902+
call_str += f"timestep, {var_locname}{initial_value_string})"
903+
else:
904+
# Replace vertical dimension with local name
905+
call_str = "call read_field(file, " + \
906+
f"'{var_stdname}', input_var_names(:,name_idx), "
907+
if levnm is not None:
908+
call_str += f"'{levnm}', "
909+
# end if
910+
err_on_not_found_string = ""
911+
if var_stdname in vars_init_value:
912+
# if initial value is available, do not throw error when not found in initial condition file.
913+
err_on_not_found_string = ", error_on_not_found=.false."
914+
# end if
915+
call_str += f"timestep, {var_locname}{err_on_not_found_string})"
875916
# end if
876-
call_str += f"timestep, {var_locname}{err_on_not_found_string})"
877917
else:
878918
# if initial value is assigned, then it can be ignored
879919
if var_stdname in vars_init_value:
@@ -903,7 +943,8 @@ def write_phys_read_subroutine(outfile, host_dict, host_vars, host_imports,
903943
["shr_kind_mod", ["SHR_KIND_CS, SHR_KIND_CL, SHR_KIND_CX"]],
904944
["physics_data", ["read_field", "find_input_name_idx",
905945
"no_exist_idx", "init_mark_idx",
906-
"prot_no_init_idx", "const_idx"]],
946+
"prot_no_init_idx", "const_idx",
947+
"read_constituent_dimensioned_field"]],
907948
["cam_ccpp_cap", ["ccpp_physics_suite_variables",
908949
"cam_constituents_array",
909950
"cam_model_const_properties"]],
@@ -967,8 +1008,13 @@ def write_phys_read_subroutine(outfile, host_dict, host_vars, host_imports,
9671008
outfile.write("logical :: use_init_variables", 2)
9681009
outfile.blank_line()
9691010

1011+
# Prepare constituent properties pointer for later usage:
1012+
outfile.comment("Get constituent properties pointer:", 2)
1013+
outfile.write("const_props => cam_model_const_properties()", 2)
1014+
outfile.blank_line()
1015+
9701016
# Initialize variables:
971-
outfile.comment("Initalize missing and non-initialized variables strings:",
1017+
outfile.comment("Initialize missing and non-initialized variables strings:",
9721018
2)
9731019
outfile.write("missing_required_vars = ' '", 2)
9741020
outfile.write("protected_non_init_vars = ' '", 2)
@@ -1103,7 +1149,6 @@ def write_phys_read_subroutine(outfile, host_dict, host_vars, host_imports,
11031149
# Read in constituent data
11041150
outfile.comment("Read in constituent variables if not using init variables", 2)
11051151
outfile.write("field_data_ptr => cam_constituents_array()", 2)
1106-
outfile.write("const_props => cam_model_const_properties()", 2)
11071152
outfile.blank_line()
11081153
outfile.comment("Iterate over all registered constituents", 2)
11091154
outfile.write("do constituent_idx = 1, size(const_props)", 2)
@@ -1191,7 +1236,7 @@ def write_phys_check_subroutine(outfile, host_dict, host_vars, host_imports,
11911236
call_string_key = f"case ('{var_stdname}')"
11921237

11931238
# Extract vertical level variable:
1194-
levnm, call_check_field, reason = get_dimension_info(hvar)
1239+
levnm, call_check_field, reason, has_constituent_read = get_dimension_info(hvar)
11951240

11961241
# Set "check_field" call string:
11971242
if call_check_field:

src/physics/utils/physics_data.F90

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ module physics_data
2626
module procedure check_field_3d
2727
end interface check_field
2828

29+
interface read_constituent_dimensioned_field
30+
module procedure read_constituent_dimensioned_field_2d
31+
end interface read_constituent_dimensioned_field
32+
2933
!==============================================================================
3034
CONTAINS
3135
!==============================================================================
@@ -325,6 +329,197 @@ subroutine read_field_3d(file, std_name, var_names, vcoord_name, &
325329
end if
326330
end subroutine read_field_3d
327331

332+
subroutine read_constituent_dimensioned_field_2d(const_props, file, std_name, base_var_names, timestep, field_array, initial_value)
333+
use shr_assert_mod, only: shr_assert_in_domain
334+
use shr_sys_mod, only: shr_sys_flush
335+
use pio, only: file_desc_t, var_desc_t
336+
use spmd_utils, only: masterproc
337+
use cam_pio_utils, only: cam_pio_find_var
338+
use cam_abortutils, only: endrun
339+
use cam_logfile, only: iulog
340+
use cam_field_read, only: cam_read_field
341+
use ccpp_constituent_prop_mod, only: ccpp_constituent_prop_ptr_t
342+
use phys_vars_init_check, only: mark_as_read_from_file
343+
use phys_vars_init_check, only: phys_var_stdnames, input_var_names, phys_var_num
344+
345+
! Dummy arguments
346+
type(ccpp_constituent_prop_ptr_t), intent(in) :: const_props(:) ! Constituent properties
347+
type(file_desc_t), intent(inout) :: file
348+
character(len=*), intent(in) :: std_name ! Standard name of base variable.
349+
character(len=*), intent(in) :: base_var_names(:) ! "Base" name(s) used to construct variable name (base_constname)
350+
integer, intent(in) :: timestep ! Timestep to read [count]
351+
real(kind_phys), intent(inout) :: field_array(:,:) ! Output field array (ncol, pcnst)
352+
real(kind_phys), optional, intent(in) :: initial_value ! Default value if not found
353+
354+
! Local variables
355+
logical :: var_found
356+
character(len=128) :: constituent_name
357+
character(len=256) :: file_var_name
358+
character(len=256) :: found_name
359+
character(len=512) :: missing_vars
360+
type(var_desc_t) :: vardesc
361+
real(kind_phys), allocatable :: buffer(:)
362+
integer :: const_idx, base_idx
363+
integer :: ierr
364+
real(kind_phys) :: default_value
365+
logical :: has_initial_value
366+
logical :: any_missing
367+
368+
! For construction of constituent short name mapping
369+
character(len=128), allocatable :: constituent_short_names(:)
370+
character(len=128) :: constituent_std_name
371+
integer :: n
372+
integer :: const_input_idx
373+
374+
character(len=*), parameter :: subname = 'read_constituent_dimensioned_field: '
375+
376+
! Check if initial value was provided
377+
has_initial_value = present(initial_value)
378+
379+
! Initialize tracking variables
380+
any_missing = .false.
381+
missing_vars = ''
382+
383+
! Allocate temporary buffer
384+
allocate(buffer(size(field_array, 1)), stat=ierr)
385+
if (ierr /= 0) then
386+
call endrun(subname//'Failed to allocate buffer')
387+
end if
388+
389+
!REMOVECAM:
390+
! Because the constituent properties pointer contains standard names, and not input constituent names
391+
! (e.g., Q, CLDLIQ, ...) which are used in the input file names,
392+
! we have to construct a mapping of the standard names to the short input IC file names
393+
! When CAM is retired and only standard names are used for constituents, this mapping can be removed.
394+
allocate(constituent_short_names(size(const_props)), stat=ierr)
395+
if (ierr /= 0) then
396+
call endrun(subname//'Failed to allocate constituent_short_names')
397+
end if
398+
399+
const_shortmap_loop: do const_idx = 1, size(const_props)
400+
! Get constituent standard name.
401+
call const_props(const_idx)%standard_name(constituent_std_name)
402+
403+
! Check if constituent standard name is in the registry to look up its IC name
404+
! n.b. this assumes that the first IC name is the short name
405+
const_input_idx = -1
406+
phys_inputvar_loop: do n = 1, phys_var_num
407+
if (trim(phys_var_stdnames(n)) == trim(constituent_std_name)) then
408+
const_input_idx = n
409+
exit phys_inputvar_loop
410+
end if
411+
end do
412+
413+
if (const_input_idx > 0) then
414+
! Use the first entry from the input_var_names -- assumed to be short name.
415+
constituent_short_names(const_idx) = trim(input_var_names(1, const_input_idx))
416+
else
417+
! Use the standard name itself if not found in registry.
418+
constituent_short_names(const_idx) = trim(constituent_std_name)
419+
end if
420+
end do const_shortmap_loop
421+
!END REMOVECAM
422+
423+
! Initialize field array to default value (only if initial_value provided)
424+
if (has_initial_value) then
425+
field_array(:,:) = initial_value
426+
end if
427+
428+
! Loop through all possible base names to find correct base name.
429+
! Note this assumes that the same base name is used for all constituents.
430+
! i.e., there cannot be something like cam_in_cflx_Q & cflx_CLDLIQ in one file.
431+
base_idx_loop: do base_idx = 1, size(base_var_names)
432+
! Loop through all constituents
433+
const_idx_loop: do const_idx = 1, size(const_props)
434+
! Get constituent short name
435+
constituent_name = constituent_short_names(const_idx)
436+
437+
! Create file variable name: <base_var_name>_<constituent_name>
438+
file_var_name = trim(base_var_names(base_idx)) // '_' // trim(constituent_name)
439+
440+
! Try to find variable in file
441+
var_found = .false.
442+
call cam_pio_find_var(file, [file_var_name], found_name, vardesc, var_found)
443+
444+
if(var_found) then
445+
exit base_idx_loop
446+
endif
447+
end do const_idx_loop
448+
end do base_idx_loop
449+
450+
! Once base_idx is identified, use it in the actual constituent loop:
451+
const_read_loop: do const_idx = 1, size(const_props)
452+
! Get constituent short name
453+
constituent_name = constituent_short_names(const_idx)
454+
455+
! Create file variable name: <base_var_name>_<constituent_name>
456+
file_var_name = trim(base_var_names(base_idx)) // '_' // trim(constituent_name)
457+
458+
! Try to find variable in file
459+
var_found = .false.
460+
call cam_pio_find_var(file, [file_var_name], found_name, vardesc, var_found)
461+
462+
if (var_found) then
463+
! Read the variable
464+
if (masterproc) then
465+
write(iulog, *) 'Reading constituent-dimensioned input field, ', trim(found_name)
466+
call shr_sys_flush(iulog)
467+
end if
468+
469+
call cam_read_field(found_name, file, buffer, var_found, timelevel=timestep)
470+
471+
if (var_found) then
472+
! Copy to correct constituent index in field array
473+
field_array(:, const_idx) = buffer(:)
474+
475+
! Check for NaN values
476+
call shr_assert_in_domain(field_array(:, const_idx), is_nan=.false., &
477+
varname=trim(found_name), &
478+
msg=subname//'NaN found in '//trim(found_name))
479+
else
480+
! Failed to read even though variable was found
481+
any_missing = .true.
482+
if (len_trim(missing_vars) > 0) then
483+
missing_vars = trim(missing_vars) // ', ' // trim(file_var_name)
484+
else
485+
missing_vars = trim(file_var_name)
486+
end if
487+
end if
488+
else
489+
! Variable not found in file
490+
any_missing = .true.
491+
if (len_trim(missing_vars) > 0) then
492+
missing_vars = trim(missing_vars) // ', ' // trim(file_var_name)
493+
else
494+
missing_vars = trim(file_var_name)
495+
end if
496+
497+
if (has_initial_value) then
498+
! Use default value (already set above)
499+
500+
if (masterproc) then
501+
write(iulog, *) 'Constituent-dimensioned field ', trim(file_var_name), &
502+
' not found, using default value for constituent ', trim(constituent_name)
503+
call shr_sys_flush(iulog)
504+
end if
505+
end if
506+
end if
507+
end do const_read_loop
508+
509+
! Check if we should fail due to missing variables
510+
if (any_missing .and. .not. has_initial_value) then
511+
call endrun(subname//'Required constituent-dimensioned variables not found: ' // trim(missing_vars))
512+
end if
513+
514+
! Mark the base variable as read from file (only if no errors)
515+
call mark_as_read_from_file(std_name)
516+
517+
! Clean up
518+
deallocate(constituent_short_names)
519+
deallocate(buffer)
520+
521+
end subroutine read_constituent_dimensioned_field_2d
522+
328523
subroutine check_field_2d(file, var_names, timestep, current_value, &
329524
stdname, min_difference, min_relative_value, is_first, diff_found)
330525
use pio, only: file_desc_t, var_desc_t

0 commit comments

Comments
 (0)