Skip to content

Commit edf84cd

Browse files
authored
Implement run phase and dynamics-physics coupling for MPAS dynamical core (#304)
### Tag name (required for release branches): None ### Originator(s): kuanchihwang ### Summary (include the keyword ['closes', 'fixes', 'resolves'] and issue number): Implement run (i.e., time integration) phase and dynamics-physics coupling for MPAS dynamical core. **CAM-SIMA with MPAS dynamical core is now able to run toward the end successfully.** ### Describe any changes made to build system: None ### Describe any changes made to the namelist: None ### List any changes to the defaults for the input datasets (e.g. boundary datasets): None ### List all files eliminated and why: None ### List all files added and what they do: * `A src/dynamics/mpas/dyn_coupling.F90` * Implement dynamics-physics coupling and vice versa ### List all existing files that have been modified, and describe the changes: * `M src/dynamics/mpas/driver/dyn_mpas_subdriver.F90` * Implement MPAS subdriver * Support for computing edge wind tendency in MPAS subdriver * `M src/dynamics/mpas/dyn_comp.F90` * Support for computing edge wind tendency in MPAS subdriver * Implement `dyn_run` * Sort `use` statements and add comments * Factor out variable finalization * Implement `reverse` * Refactor assignments between CAM-SIMA and MPAS in terms of `reverse` * Implement `dyn_exchange_constituent_state` * Switch to use `dyn_exchange_constituent_state` * `M src/dynamics/mpas/stepon.F90` * Sort `use` statements and adjust indentation * Wire up `dyn_run` * Wire up dynamics-physics coupling
2 parents d1b20c9 + 51a30e7 commit edf84cd

File tree

4 files changed

+1076
-120
lines changed

4 files changed

+1076
-120
lines changed

src/dynamics/mpas/driver/dyn_mpas_subdriver.F90

Lines changed: 160 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ module dyn_mpas_subdriver
2121
pio_inq_varid, pio_inq_varndims, pio_inq_vartype, pio_noerr
2222

2323
! Modules from MPAS.
24-
use atm_core, only: atm_mpas_init_block
24+
use atm_core, only: atm_compute_output_diagnostics, atm_do_timestep, atm_mpas_init_block
2525
use atm_core_interface, only: atm_setup_core, atm_setup_domain
2626
use atm_time_integration, only: mpas_atm_dynamics_init
2727
use mpas_atm_dimensions, only: mpas_atm_set_dims
@@ -34,14 +34,15 @@ module dyn_mpas_subdriver
3434
mpas_pool_type, mpas_pool_field_info_type, &
3535
mpas_pool_character, mpas_pool_real, mpas_pool_integer, &
3636
mpas_stream_type, mpas_stream_noerr, &
37-
mpas_time_type, mpas_start_time, &
37+
mpas_time_type, mpas_timeinterval_type, mpas_now, mpas_start_time, &
3838
mpas_io_native_precision, mpas_io_pnetcdf, mpas_io_read, mpas_io_write, &
3939
field0dchar, field1dchar, &
4040
field0dinteger, field1dinteger, field2dinteger, field3dinteger, &
4141
field0dreal, field1dreal, field2dreal, field3dreal, field4dreal, field5dreal
4242
use mpas_dmpar, only: mpas_dmpar_exch_halo_field, &
4343
mpas_dmpar_max_int, mpas_dmpar_sum_int
4444
use mpas_domain_routines, only: mpas_allocate_domain
45+
use mpas_field_routines, only: mpas_allocate_scratch_field
4546
use mpas_framework, only: mpas_framework_init_phase1, mpas_framework_init_phase2
4647
use mpas_io_streams, only: mpas_createstream, mpas_closestream, mpas_streamaddfield, &
4748
mpas_readstream, mpas_writestream, mpas_writestreamatt
@@ -51,11 +52,13 @@ module dyn_mpas_subdriver
5152
mpas_pool_get_array, &
5253
mpas_pool_add_dimension, mpas_pool_get_dimension, &
5354
mpas_pool_get_field, mpas_pool_get_field_info, &
54-
mpas_pool_initialize_time_levels
55+
mpas_pool_initialize_time_levels, mpas_pool_shift_time_levels
5556
use mpas_stream_inquiry, only: mpas_stream_inquiry_new_streaminfo
5657
use mpas_stream_manager, only: postread_reindex, prewrite_reindex, postwrite_reindex
5758
use mpas_string_utils, only: mpas_string_replace
58-
use mpas_timekeeping, only: mpas_get_clock_time, mpas_get_time
59+
use mpas_timekeeping, only: mpas_advance_clock, mpas_get_clock_time, mpas_get_time, &
60+
mpas_set_timeinterval, &
61+
operator(+), operator(<)
5962
use mpas_vector_operations, only: mpas_initialize_vectors
6063

6164
implicit none
@@ -106,7 +109,8 @@ end subroutine model_error_if
106109
logical, allocatable :: is_water_species(:)
107110

108111
! Initialized by `dyn_mpas_init_phase4`.
109-
integer :: coupling_time_interval
112+
integer :: coupling_time_interval = 0
113+
integer :: number_of_time_steps = 0
110114
contains
111115
private
112116

@@ -123,6 +127,7 @@ end subroutine model_error_if
123127
procedure, pass, public :: compute_unit_vector => dyn_mpas_compute_unit_vector
124128
procedure, pass, public :: compute_edge_wind => dyn_mpas_compute_edge_wind
125129
procedure, pass, public :: init_phase4 => dyn_mpas_init_phase4
130+
procedure, pass, public :: run => dyn_mpas_run
126131

127132
! Accessor subroutines for users to access internal states of MPAS dynamical core.
128133

@@ -2525,19 +2530,21 @@ end subroutine dyn_mpas_compute_unit_vector
25252530
!> \date 16 January 2020
25262531
!> \details
25272532
!> This subroutine computes the edge-normal wind vectors at edge points
2528-
!> (i.e., `u` in MPAS `state` pool) from wind components at cell points
2533+
!> (i.e., `u` in MPAS `state` pool) from the wind components at cell points
25292534
!> (i.e., `uReconstruct{Zonal,Meridional}` in MPAS `diag` pool). In MPAS, the
25302535
!> former are PROGNOSTIC variables, while the latter are DIAGNOSTIC variables
25312536
!> that are "reconstructed" from the former. This subroutine is essentially the
25322537
!> inverse function of that reconstruction. The purpose is to provide an
25332538
!> alternative way for MPAS to initialize from zonal and meridional wind
2534-
!> components at cell points.
2539+
!> components at cell points. If `wind_tendency` is `.true.`, this subroutine
2540+
!> operates on the wind tendency due to physics instead.
25352541
!> \addenda
25362542
!> Ported and refactored for CAM-SIMA. (KCW, 2024-05-08)
25372543
!
25382544
!-------------------------------------------------------------------------------
2539-
subroutine dyn_mpas_compute_edge_wind(self)
2545+
subroutine dyn_mpas_compute_edge_wind(self, wind_tendency)
25402546
class(mpas_dynamical_core_type), intent(in) :: self
2547+
logical, intent(in) :: wind_tendency
25412548

25422549
character(*), parameter :: subname = 'dyn_mpas_subdriver::dyn_mpas_compute_edge_wind'
25432550
integer :: cell1, cell2, i
@@ -2561,22 +2568,36 @@ subroutine dyn_mpas_compute_edge_wind(self)
25612568
nullify(uedge)
25622569

25632570
! Make sure halo layers are up-to-date before computation.
2564-
call self % exchange_halo('uReconstructZonal')
2565-
call self % exchange_halo('uReconstructMeridional')
2571+
if (wind_tendency) then
2572+
call self % exchange_halo('tend_uzonal')
2573+
call self % exchange_halo('tend_umerid')
2574+
else
2575+
call self % exchange_halo('uReconstructZonal')
2576+
call self % exchange_halo('uReconstructMeridional')
2577+
end if
25662578

25672579
! Input.
25682580
call self % get_variable_pointer(nedges, 'dim', 'nEdges')
25692581

2570-
call self % get_variable_pointer(ucellzonal, 'diag', 'uReconstructZonal')
2571-
call self % get_variable_pointer(ucellmeridional, 'diag', 'uReconstructMeridional')
2582+
if (wind_tendency) then
2583+
call self % get_variable_pointer(ucellzonal, 'tend_physics', 'tend_uzonal')
2584+
call self % get_variable_pointer(ucellmeridional, 'tend_physics', 'tend_umerid')
2585+
else
2586+
call self % get_variable_pointer(ucellzonal, 'diag', 'uReconstructZonal')
2587+
call self % get_variable_pointer(ucellmeridional, 'diag', 'uReconstructMeridional')
2588+
end if
25722589

25732590
call self % get_variable_pointer(cellsonedge, 'mesh', 'cellsOnEdge')
25742591
call self % get_variable_pointer(east, 'mesh', 'east')
25752592
call self % get_variable_pointer(north, 'mesh', 'north')
25762593
call self % get_variable_pointer(edgenormalvectors, 'mesh', 'edgeNormalVectors')
25772594

25782595
! Output.
2579-
call self % get_variable_pointer(uedge, 'state', 'u', time_level=1)
2596+
if (wind_tendency) then
2597+
call self % get_variable_pointer(uedge, 'tend_physics', 'tend_ru_physics')
2598+
else
2599+
call self % get_variable_pointer(uedge, 'state', 'u', time_level=1)
2600+
end if
25802601

25812602
do i = 1, nedges
25822603
cell1 = cellsonedge(1, i)
@@ -2607,7 +2628,11 @@ subroutine dyn_mpas_compute_edge_wind(self)
26072628
nullify(uedge)
26082629

26092630
! Make sure halo layers are up-to-date after computation.
2610-
call self % exchange_halo('u')
2631+
if (wind_tendency) then
2632+
call self % exchange_halo('tend_ru_physics')
2633+
else
2634+
call self % exchange_halo('u')
2635+
end if
26112636

26122637
call self % debug_print(subname // ' completed')
26132638
end subroutine dyn_mpas_compute_edge_wind
@@ -2640,6 +2665,7 @@ subroutine dyn_mpas_init_phase4(self, coupling_time_interval)
26402665
logical, pointer :: config_do_restart
26412666
real(rkind), pointer :: config_dt
26422667
type(field0dreal), pointer :: field_0d_real
2668+
type(field2dreal), pointer :: field_2d_real
26432669
type(mpas_pool_type), pointer :: mpas_pool
26442670
type(mpas_time_type) :: mpas_time
26452671

@@ -2651,6 +2677,7 @@ subroutine dyn_mpas_init_phase4(self, coupling_time_interval)
26512677
nullify(config_do_restart)
26522678
nullify(config_dt)
26532679
nullify(field_0d_real)
2680+
nullify(field_2d_real)
26542681
nullify(mpas_pool)
26552682

26562683
if (coupling_time_interval <= 0) then
@@ -2673,6 +2700,7 @@ subroutine dyn_mpas_init_phase4(self, coupling_time_interval)
26732700
end if
26742701

26752702
self % coupling_time_interval = coupling_time_interval
2703+
self % number_of_time_steps = 0
26762704

26772705
call self % debug_print('Coupling time interval is ' // stringify([real(self % coupling_time_interval, rkind)]) // &
26782706
' seconds')
@@ -2814,6 +2842,16 @@ subroutine dyn_mpas_init_phase4(self, coupling_time_interval)
28142842
! Prepare dynamics for time integration.
28152843
call mpas_atm_dynamics_init(self % domain_ptr)
28162844

2845+
! Some additional "scratch" fields are needed for interoperability with CAM-SIMA, but they are not initialized by
2846+
! `mpas_atm_dynamics_init`. Initialize them below.
2847+
call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, 'tend_uzonal', field_2d_real, timelevel=1)
2848+
call mpas_allocate_scratch_field(field_2d_real)
2849+
nullify(field_2d_real)
2850+
2851+
call mpas_pool_get_field(self % domain_ptr % blocklist % allfields, 'tend_umerid', field_2d_real, timelevel=1)
2852+
call mpas_allocate_scratch_field(field_2d_real)
2853+
nullify(field_2d_real)
2854+
28172855
call self % debug_print(subname // ' completed')
28182856

28192857
call self % debug_print('Successful initialization of MPAS dynamical core')
@@ -2867,6 +2905,113 @@ pure function almost_equal(a, b, absolute_tolerance, relative_tolerance)
28672905
end function almost_equal
28682906
end subroutine dyn_mpas_init_phase4
28692907

2908+
!-------------------------------------------------------------------------------
2909+
! subroutine dyn_mpas_run
2910+
!
2911+
!> \brief Integrates the dynamical states with time
2912+
!> \author Michael Duda
2913+
!> \date 29 February 2020
2914+
!> \details
2915+
!> This subroutine calls MPAS dynamical solver in a loop, with each iteration
2916+
!> of the loop advancing the dynamical states forward by one time step, until
2917+
!> the coupling time interval is reached.
2918+
!> Essentially, it closely follows what is done in `atm_core_run`, but without
2919+
!> any calls to MPAS diagnostics manager or MPAS stream manager.
2920+
!> \addenda
2921+
!> Ported and refactored for CAM-SIMA. (KCW, 2024-06-21)
2922+
!
2923+
!-------------------------------------------------------------------------------
2924+
subroutine dyn_mpas_run(self)
2925+
class(mpas_dynamical_core_type), intent(inout) :: self
2926+
2927+
character(*), parameter :: subname = 'dyn_mpas_subdriver::dyn_mpas_run'
2928+
character(strkind) :: date_time
2929+
integer :: ierr
2930+
real(rkind), pointer :: config_dt
2931+
type(mpas_pool_type), pointer :: mpas_pool_diag, mpas_pool_mesh, mpas_pool_state
2932+
type(mpas_time_type) :: mpas_time_end, mpas_time_now ! This derived type is analogous to `ESMF_Time`.
2933+
type(mpas_timeinterval_type) :: mpas_time_interval ! This derived type is analogous to `ESMF_TimeInterval`.
2934+
2935+
call self % debug_print(subname // ' entered')
2936+
2937+
nullify(config_dt)
2938+
nullify(mpas_pool_diag, mpas_pool_mesh, mpas_pool_state)
2939+
2940+
call self % get_variable_pointer(config_dt, 'cfg', 'config_dt')
2941+
call self % get_pool_pointer(mpas_pool_diag, 'diag')
2942+
call self % get_pool_pointer(mpas_pool_mesh, 'mesh')
2943+
call self % get_pool_pointer(mpas_pool_state, 'state')
2944+
2945+
mpas_time_now = mpas_get_clock_time(self % domain_ptr % clock, mpas_now, ierr=ierr)
2946+
2947+
if (ierr /= 0) then
2948+
call self % model_error('Failed to get time for "mpas_now"', subname, __LINE__)
2949+
end if
2950+
2951+
call mpas_get_time(mpas_time_now, datetimestring=date_time, ierr=ierr)
2952+
2953+
if (ierr /= 0) then
2954+
call self % model_error('Failed to get time for "mpas_now"', subname, __LINE__)
2955+
end if
2956+
2957+
call self % debug_print('Time integration of MPAS dynamical core begins at ' // trim(adjustl(date_time)))
2958+
2959+
call mpas_set_timeinterval(mpas_time_interval, s=self % coupling_time_interval, ierr=ierr)
2960+
2961+
if (ierr /= 0) then
2962+
call self % model_error('Failed to set coupling time interval', subname, __LINE__)
2963+
end if
2964+
2965+
! The `+` operator is overloaded here.
2966+
mpas_time_end = mpas_time_now + mpas_time_interval
2967+
2968+
! Integrate until the coupling time interval is reached.
2969+
! The `<` operator is overloaded here.
2970+
do while (mpas_time_now < mpas_time_end)
2971+
! Number of time steps that has been completed in this MPAS dynamical core instance.
2972+
self % number_of_time_steps = self % number_of_time_steps + 1
2973+
2974+
! Advance the dynamical states forward in time by `config_dt` seconds.
2975+
! Current states are in time level 1. Upon exit, time level 2 will contain updated states.
2976+
call atm_do_timestep(self % domain_ptr, config_dt, self % number_of_time_steps)
2977+
2978+
! MPAS `state` pool has two time levels.
2979+
! Swap them after advancing a time step.
2980+
call mpas_pool_shift_time_levels(mpas_pool_state)
2981+
2982+
call mpas_advance_clock(self % domain_ptr % clock, ierr=ierr)
2983+
2984+
if (ierr /= 0) then
2985+
call self % model_error('Failed to advance clock', subname, __LINE__)
2986+
end if
2987+
2988+
mpas_time_now = mpas_get_clock_time(self % domain_ptr % clock, mpas_now, ierr=ierr)
2989+
2990+
if (ierr /= 0) then
2991+
call self % model_error('Failed to get time for "mpas_now"', subname, __LINE__)
2992+
end if
2993+
2994+
call self % debug_print('Time step ' // stringify([self % number_of_time_steps]) // ' completed')
2995+
end do
2996+
2997+
call mpas_get_time(mpas_time_now, datetimestring=date_time, ierr=ierr)
2998+
2999+
if (ierr /= 0) then
3000+
call self % model_error('Failed to get time for "mpas_now"', subname, __LINE__)
3001+
end if
3002+
3003+
call self % debug_print('Time integration of MPAS dynamical core ends at ' // trim(adjustl(date_time)))
3004+
3005+
! Compute diagnostic variables like `pressure`, `rho` and `theta` from time level 1 of MPAS `state` pool
3006+
! by calling upstream MPAS functionality.
3007+
call atm_compute_output_diagnostics(mpas_pool_state, 1, mpas_pool_diag, mpas_pool_mesh)
3008+
3009+
nullify(config_dt)
3010+
nullify(mpas_pool_diag, mpas_pool_mesh, mpas_pool_state)
3011+
3012+
call self % debug_print(subname // ' completed')
3013+
end subroutine dyn_mpas_run
3014+
28703015
!-------------------------------------------------------------------------------
28713016
! function dyn_mpas_get_constituent_name
28723017
!
@@ -3148,7 +3293,7 @@ subroutine dyn_mpas_get_pool_pointer(self, pool_pointer, pool_name)
31483293
pool_pointer => self % domain_ptr % configs
31493294
case ('dim')
31503295
pool_pointer => self % domain_ptr % blocklist % dimensions
3151-
case ('diag', 'mesh', 'state', 'tend')
3296+
case ('diag', 'mesh', 'state', 'tend', 'tend_physics')
31523297
call mpas_pool_get_subpool(self % domain_ptr % blocklist % allstructs, trim(adjustl(pool_name)), pool_pointer)
31533298
case default
31543299
call self % model_error('Unsupported pool name "' // trim(adjustl(pool_name)) // '"', subname, __LINE__)

0 commit comments

Comments
 (0)