diff --git a/components/lfric-xios/source/lfric_xios_context_mod.f90 b/components/lfric-xios/source/lfric_xios_context_mod.f90 index 7f3d0e429..908fe376e 100644 --- a/components/lfric-xios/source/lfric_xios_context_mod.f90 +++ b/components/lfric-xios/source/lfric_xios_context_mod.f90 @@ -15,6 +15,9 @@ module lfric_xios_context_mod use field_mod, only : field_type use file_mod, only : file_type use io_context_mod, only : io_context_type, callback_clock_arg + use io_config_mod, only : file_convention, & + file_convention_ugrid, & + file_convention_cf use lfric_xios_file_mod, only : lfric_xios_file_type use lfric_mpi_mod, only : lfric_comm_type use log_mod, only : log_event, log_scratch_space, & @@ -24,6 +27,7 @@ module lfric_xios_context_mod setup_xios_files use lfric_xios_file_mod, only : lfric_xios_file_type use linked_list_mod, only : linked_list_type, linked_list_item_type + use mesh_mod, only : mesh_type use model_clock_mod, only : model_clock_type use timer_mod, only : timer use xios, only : xios_context, & @@ -49,6 +53,9 @@ module lfric_xios_context_mod logical :: uses_timer = .false. logical :: xios_context_initialised = .false. + !> Flag denoting if this file is a UGRID Planar mesh file with + !> projected coordinates that have been scaled + logical :: ugrid_scaled_projected_coordinates = .false. contains private @@ -112,6 +119,7 @@ subroutine initialise_xios_context( this, communicator, & type(field_type), optional, intent(in) :: alt_panel_ids(:) logical, optional, intent(in) :: start_at_zero + type(mesh_type), pointer :: mesh => null() type(linked_list_item_type), pointer :: loop => null() type(lfric_xios_file_type), pointer :: file => null() logical :: zero_start @@ -133,8 +141,17 @@ subroutine initialise_xios_context( this, communicator, & ! Run XIOS setup routines call init_xios_calendar(model_clock, calendar, zero_start, this%context_clock_step) + call init_xios_dimensions(chi, panel_id, alt_coords, alt_panel_ids) - if (this%filelist%get_length() > 0) call setup_xios_files(this%filelist) + ! Obtain information on whether the mesh is ugrid and planar here? + ! This is to inform decisions on file post processing work around code path. + mesh => chi(1)%get_mesh() + if ( mesh%is_geometry_planar() .and. & + file_convention == file_convention_ugrid ) then + this%ugrid_scaled_projected_coordinates = .true. + end if + if (this%filelist%get_length() > 0) call setup_xios_files(this%filelist, & + this%ugrid_scaled_projected_coordinates) if (associated(before_close)) call before_close(model_clock) @@ -197,22 +214,28 @@ subroutine finalise_xios_context( this ) call log_event(log_scratch_space, log_level_debug) call xios_context_finalize() - ! We have closed the context on our end, but we need to make sure that XIOS - ! has closed the files for all servers before we process them. - call init_wait() - - ! Close all files in list - if (this%filelist%get_length() > 0) then - loop => this%filelist%get_head() - do while (associated(loop)) - select type( list_item => loop%payload ) - type is (lfric_xios_file_type) - file => list_item - call file%file_close() - end select - loop => loop%next - end do + ! Only take action if this is a regional model with UGRID Projected + ! coordinates, as these are awaiting XIOS feature development + if ( this%ugrid_scaled_projected_coordinates ) then + call log_event("Closing file for post processing.", LOG_LEVEL_DEBUG) + ! We have closed the context on our end, but we need to make sure that XIOS + ! has closed the files for all servers before we process them. + call init_wait() + + ! Close all files in list + if (this%filelist%get_length() > 0) then + loop => this%filelist%get_head() + do while (associated(loop)) + select type( list_item => loop%payload ) + type is (lfric_xios_file_type) + file => list_item + call file%file_close() + end select + loop => loop%next + end do + end if end if + this%xios_context_initialised = .false. end if nullify(loop) diff --git a/components/lfric-xios/source/lfric_xios_file_mod.f90 b/components/lfric-xios/source/lfric_xios_file_mod.f90 index cc1e9a820..43c7c24dc 100644 --- a/components/lfric-xios/source/lfric_xios_file_mod.f90 +++ b/components/lfric-xios/source/lfric_xios_file_mod.f90 @@ -89,6 +89,9 @@ module lfric_xios_file_mod logical :: is_diag = .false. !> Flag denoting if the always-on sampling mode is selected logical :: diag_always_on_sampling = .true. + !> Flag denoting if this file is a UGRID Planar mesh file with + !> projected coordinates that have been scaled + logical :: ugrid_scaled_projected_coordinates = .false. !> Will the file be read again from the beginning once the end is reached logical :: cyclic = .false. !> @@ -116,6 +119,7 @@ module lfric_xios_file_mod procedure, public :: mode_is_write procedure, public :: recv_fields procedure, public :: send_fields + procedure, public :: set_ugrid_scaled_projected_coordinates final :: lfric_xios_file_final end type lfric_xios_file_type @@ -312,10 +316,16 @@ subroutine file_close(self) if (self%is_closed) return if ( self%io_mode == FILE_MODE_WRITE ) then - call log_event( "Waiting for XIOS to close file ["//trim(self%path)//".nc]", & - log_level_debug ) - call init_wait() - call process_output_file(trim(self%path)//".nc") + ! Only take action if this is a regional model with UGRID Projected + ! coordinates, as these are awaiting XIOS feature development + if ( self%ugrid_scaled_projected_coordinates ) then + call log_event( "Waiting for XIOS to close file ["//trim(self%path)//".nc]", & + log_level_debug ) + call init_wait() + call log_event( "post processing file ["//trim(self%path)//".nc]", & + log_level_debug ) + call process_output_file(trim(self%path)//".nc") + end if end if self%is_closed = .true. @@ -570,4 +580,17 @@ subroutine lfric_xios_file_final(self) end subroutine lfric_xios_file_final +!> @brief Setter for the file object ugrid_scaled_projected_coordinates +!> @param[in] ugrid_scaled_projected_coordinates Logical +!> +subroutine set_ugrid_scaled_projected_coordinates(self, ugrid_scaled_projected_coordinates) + + implicit none + logical, intent(in) :: ugrid_scaled_projected_coordinates + class(lfric_xios_file_type), intent(inout) :: self + + self%ugrid_scaled_projected_coordinates = ugrid_scaled_projected_coordinates + +end subroutine set_ugrid_scaled_projected_coordinates + end module lfric_xios_file_mod diff --git a/components/lfric-xios/source/lfric_xios_process_output_mod.f90 b/components/lfric-xios/source/lfric_xios_process_output_mod.f90 index 81266efb4..7c3f6ff0d 100644 --- a/components/lfric-xios/source/lfric_xios_process_output_mod.f90 +++ b/components/lfric-xios/source/lfric_xios_process_output_mod.f90 @@ -4,8 +4,13 @@ ! under which the code may be used. !------------------------------------------------------------------------------- -!> @brief Module to hold routines for processing XIOS output for compliance -!> with downstream data requirements +!> @brief Module to only hold routines for re-processing files output by XIOS +!> where the configuration is unable to be delivered by XIOS. +!> The only facet in scope for this module is post processing projected +!> coordinates for UGRId specification files, pending new feature +!> development in XIOS. This is technical debt, and this +!> post processing approach should not be used for other file +!> manipulations. !> module lfric_xios_process_output_mod @@ -21,24 +26,21 @@ module lfric_xios_process_output_mod use lfric_ncdf_field_group_mod, only: lfric_ncdf_field_group_type use lfric_ncdf_file_mod, only: lfric_ncdf_file_type use lfric_xios_constants_mod, only: dp_xios - use lfric_xios_utils_mod, only: parse_date_as_xios, seconds_from_date - use log_mod, only: log_event, log_level_trace - use xios, only: xios_date, xios_get_start_date + use log_mod, only: log_event, log_level_trace, & + log_level_info implicit none - public :: process_output_file, set_xios_geometry_planar + public :: process_output_file private ! Public scaling factor for planar mesh coordinates to circumvent XIOS issue real(kind=dp_xios), public, parameter :: xyz_scaling_factor = 1.0e-4_dp_xios - logical :: model_has_planar_geometry = .false. - contains -!> @brief Processes a NetCDF file produced by XIOS to align with the LFRic -!! UGRID file format +!> @brief Processes a NetCDF file produced by XIOS to work around +!> limmitations in UGRID projected coordinates only. !> !> @param[in] file_path The path to the NetCDF file to be edited subroutine process_output_file(file_path) @@ -53,7 +55,7 @@ subroutine process_output_file(file_path) ! Output processing must be done in serial if (global_mpi%get_comm_rank() /= 0) return - call log_event("Processing output file: "//trim(file_path), log_level_trace) + call log_event("Processing output file: "//trim(file_path), log_level_info) ! If file has not been written out, then don't attempt to process it inquire(file=trim(file_path), exist=file_exists) @@ -64,41 +66,14 @@ subroutine process_output_file(file_path) open_mode=FILE_OP_OPEN, & io_mode=FILE_MODE_WRITE ) - call format_version(file_ncdf) - if (file_convention == file_convention_ugrid) then call format_mesh(file_ncdf) end if - call format_time(file_ncdf) - call file_ncdf%close_file() end subroutine process_output_file -!> @brief Tags output file with the current version number of the LFRic file -!! format -!> -!> @param[in] file_ncdf The netcdf file to be edited -subroutine format_version(file_ncdf) - - implicit none - - type(lfric_ncdf_file_type), intent(inout) :: file_ncdf - - call file_ncdf%set_attribute("description", "LFRic file format v0.2.0") - - select case(file_convention) - case (file_convention_ugrid) - call file_ncdf%set_attribute("Conventions", "UGRID-1.0") - - case (file_convention_cf) - call file_ncdf%set_attribute("Conventions", "CF") - - end select - -end subroutine format_version - !> @brief Formats the mesh object in the output file !> !> @param[in] file_ncdf The netcdf file to be edited @@ -114,83 +89,15 @@ subroutine format_mesh(file_ncdf) mesh_var = lfric_ncdf_field_type("Mesh2d", file_ncdf) - if (model_has_planar_geometry) then - call mesh_var%set_char_attribute("geometry", "planar") - call fix_planar_coordinates(file_ncdf) - else - call mesh_var%set_char_attribute("geometry", "spherical") - end if + ! This post process code should only be called on a file where + ! the model has Planar geometry and is UGRID encoded. + ! This is controlled by lfric_xios_context_mod, + ! lfric_xios_file_mod and lfric_xios_setup_mod + call fix_planar_coordinates(file_ncdf) end subroutine format_mesh -!> @brief Formats the time coordinate into CF compliant forecast metadata -!> -!> @param[in] file_ncdf The netcdf file to be edited -subroutine format_time(output_file) - - implicit none - - type(lfric_ncdf_file_type), intent(in) :: output_file - ! In the future we may need an optional argument to determine the expected - ! name of the time axis - - type(lfric_ncdf_dims_type) :: time_dims - type(lfric_ncdf_field_type) :: time_field, frt_field, fp_field - type(lfric_ncdf_field_group_type) :: fields_in_file, time_var_fields - type(xios_date) :: time_origin_date, model_start_date - character(:), allocatable :: time_var_name, time_dim_name - real(r_def), allocatable :: time_data(:), fp(:) - real(r_def) :: frt(1) - integer(i_def) :: i - - ! Set time variable and dimension names - time_var_name = "time" - time_dim_name = "time" - - ! Files that contain no time-variation will not have a time - ! variable/dimension, so they do not need to be processed - if (.not. output_file%contains_var(trim(time_var_name))) return - - ! Read the time data from the output file - time_field = lfric_ncdf_field_type(trim(time_var_name), output_file) - time_dims = lfric_ncdf_dims_type(trim(time_dim_name), output_file) - allocate(time_data(time_dims%get_size())) - call time_field%read_data(time_data) - - ! Calculate forecast metadata using XIOS calendar - time_origin_date = parse_date_as_xios( & - trim(adjustl(time_field%get_char_attribute("time_origin"))) ) - call xios_get_start_date(model_start_date) - frt(1) = seconds_from_date(model_start_date) - & - seconds_from_date(time_origin_date) - - allocate(fp(time_dims%get_size())) - do i = 1, time_dims%get_size() - fp(i) = time_data(i) - seconds_from_date(time_origin_date) - end do - - ! Setup netCDF fields for forecast metadata - frt_field = lfric_ncdf_field_type("forecast_reference_time", output_file) - call frt_field%write_data(frt) - call frt_field%set_char_attribute("units", "seconds since "// & - trim(adjustl(time_field%get_char_attribute("time_origin")))) - call frt_field%set_char_attribute("calendar", time_field%get_char_attribute("calendar")) - call frt_field%set_char_attribute("standard_name", "forecast_reference_time") - - fp_field = lfric_ncdf_field_type("forecast_period", output_file, time_dims) - call fp_field%write_data(fp) - call fp_field%set_char_attribute("units", "seconds") - call fp_field%set_char_attribute("standard_name", "forecast_period") - - ! Set forecast metadata as time coordinates - fields_in_file = lfric_ncdf_field_group_type(output_file, output_file%get_all_varids()) - time_var_fields = fields_in_file%get_group_subset(dimension=time_dims) - call time_var_fields%add_coordinate("forecast_reference_time") - call time_var_fields%add_coordinate("forecast_period") - -end subroutine format_time - !> @brief Fixes issues with planar coordinates in output file !> !> @param[in] file_ncdf The netcdf file to be edited @@ -224,13 +131,4 @@ subroutine fix_planar_coordinates(file_ncdf) end subroutine fix_planar_coordinates -!> @brief Specifies that the model is running on a mesh with planar geometry -subroutine set_xios_geometry_planar() - - implicit none - - model_has_planar_geometry = .true. - -end subroutine set_xios_geometry_planar - end module lfric_xios_process_output_mod diff --git a/components/lfric-xios/source/lfric_xios_setup_mod.x90 b/components/lfric-xios/source/lfric_xios_setup_mod.x90 index 9c762d7bb..96e12f4cd 100644 --- a/components/lfric-xios/source/lfric_xios_setup_mod.x90 +++ b/components/lfric-xios/source/lfric_xios_setup_mod.x90 @@ -28,8 +28,7 @@ module lfric_xios_setup_mod file_convention_cf use lfric_mpi_mod, only: global_mpi use lfric_xios_file_mod, only: lfric_xios_file_type - use lfric_xios_process_output_mod, only: set_xios_geometry_planar, & - xyz_scaling_factor + use lfric_xios_process_output_mod, only: xyz_scaling_factor use lfric_xios_utils_mod, only: set_prime_io_mesh, parse_date_as_xios use linked_list_mod, only: linked_list_type, linked_list_item_type use log_mod, only: log_event, log_level_error, & @@ -44,32 +43,38 @@ module lfric_xios_setup_mod use sci_pointwise_convert_xyz2llr_kernel_mod, & only: pointwise_convert_xyz2llr_kernel_type use step_calendar_mod, only: step_calendar_type - use xios, only: xios_date, & - xios_duration, & - xios_define_calendar, & - xios_set_timestep, & - xios_set_start_date, & - xios_get_attr, & - xios_get_handle, & - xios_set_attr, & - xios_set_axis_attr, & - xios_set_domain_attr, & - xios_get_domain_attr, & - xios_add_child, & - xios_domain, & - xios_domaingroup, & - xios_is_valid_domain, & - xios_axis, & - xios_axisgroup, & - xios_grid, & - xios_gridgroup, & - xios_is_valid_grid, & - xios_file, & - xios_filegroup, & - xios_is_valid_file, & - xios_field, & - xios_fieldgroup, & - operator(+), operator(<) + use xios, only: xios_date, & + xios_date_convert_to_seconds, & + xios_date_convert_to_string, & + xios_duration, & + xios_define_calendar, & + xios_set_timestep, & + xios_set_start_date, & + xios_get_attr, & + xios_get_calendar_type, & + xios_get_handle, & + xios_set_attr, & + xios_set_axis_attr, & + xios_set_domain_attr, & + xios_set_file_attr, & + xios_set_scalar_attr, & + xios_get_domain_attr, & + xios_add_child, & + xios_domain, & + xios_domaingroup, & + xios_is_valid_domain, & + xios_is_valid_scalar, & + xios_axis, & + xios_axisgroup, & + xios_grid, & + xios_gridgroup, & + xios_is_valid_grid, & + xios_file, & + xios_filegroup, & + xios_is_valid_file, & + xios_field, & + xios_fieldgroup, & + operator(+), operator(<), operator(-) !>@todo This information should be obtained from the calendar in the future use time_config_mod, only: type_of_calendar => calendar_type, & @@ -99,6 +104,10 @@ contains timestep_length_for_xios type(xios_date) :: xios_start_date, xios_origin_date integer(i_timestep) :: step_offset + type(xios_date) :: reference_time + double precision :: frtv + character(len=64) :: str_origin, calendar_label + character(len=76) :: str_calendar ! Set the XIOS time-step from the model clock timestep_length_for_xios%second = model_clock%get_seconds_per_step() @@ -146,6 +155,22 @@ contains "date_convert_to_seconds", log_level_warning) end if + ! Configure the forecast reference time scalar coordinate variable: + ! if the scalar is available and valid within the context. + if ( xios_is_valid_scalar("frt") ) then + call xios_date_convert_to_string(xios_origin_date, str_origin) + call xios_set_scalar_attr("frt", unit="seconds since "//str_origin) + ! calculate forecast reference time in seconds from start and offset + reference_time = xios_start_date - xios_since_timestep_zero + frtv = dble(xios_date_convert_to_seconds(reference_time)) + call xios_set_scalar_attr("frt", value=frtv) + ! Note: at this point a calendar attribute should also be set + ! but, XIOS is unable to do this without feature development. + call xios_get_calendar_type(calendar_label) + str_calendar = "calendar = " // calendar_label + call xios_set_scalar_attr("frt", comment=str_calendar) + end if + end subroutine init_xios_calendar !> @brief Performs XIOS domain and axis initialisation. @@ -322,8 +347,6 @@ contains do i = 1,3 call chi(i)%copy_field_serial(sample_chi(i)) end do - ! Tell downstream output processing that the mesh is planar - call set_xios_geometry_planar() end if ! Allocate coordinate arrays @@ -421,12 +444,14 @@ contains !> @brief Sets up XIOS context file information from list of file objects !> !> @param[in] files_list List of file objects - subroutine setup_xios_files(files_list) + !> @param[in] logical ugrid_scaled_projected_coordinates + subroutine setup_xios_files(files_list, ugrid_scaled_projected_coordinates) implicit none type(linked_list_type), intent(inout) :: files_list + logical, intent(in) :: ugrid_scaled_projected_coordinates type(xios_filegroup) :: file_definition ! Pointer to linked list - used for looping through the list @@ -443,15 +468,17 @@ contains select case(file_convention) case (file_convention_ugrid) call xios_set_attr( file_definition, convention="UGRID" ) + call xios_set_attr(file_definition, convention_str="CF-1.12 UGRID-1.0") case (file_convention_cf) call xios_set_attr( file_definition, convention="CF" ) + call xios_set_attr(file_definition, convention_str="CF-1.12") case default call log_event("Invalid choice for file convention", log_level_error) end select - + call xios_set_attr(file_definition, description="LFRic file format v3.1_dev") ! Start at the head of the file linked list loop => files_list%get_head() do while(associated(loop)) @@ -462,6 +489,10 @@ contains file => list_item ! Register individual file information with context call file%register_with_context() + if ( ugrid_scaled_projected_coordinates ) then + call file%set_ugrid_scaled_projected_coordinates(ugrid_scaled_projected_coordinates) + end if + class default end select @@ -1003,7 +1034,10 @@ contains end do end if - ! Apply scaling factor for XYZ coordinates + ! Apply scaling factor for XYZ coordinates if + ! UGRID and planar (projected coordinates). + ! Post processing will be applied to these files to work around + ! XIOS limitations, pending feature development in XIOS. if ( mesh%is_geometry_planar() .and. & file_convention == file_convention_ugrid ) then lat_data = lat_data * xyz_scaling_factor