Skip to content

Commit

Permalink
Internal field index rotation
Browse files Browse the repository at this point in the history
This patch introduces an index rotation feature applied to all internal
fields.  When this feature is enabled, model fields are initialized on
the index map as specified by the configuration or input files.  The
values of these arrays are then moved to a rotated index map and saved
to a new rotated array, which are then used for model simulation.

The primary purpose of this feature is to detect regressions in
rotationally symmetric calculations, such as fluid dynamic dynamic
processes and other transport phenomena.

Fields created or read by the drivers are unrotated and are handled as
external user-defined field.  External fields are rotated or unrotated
as they pass between the driver and the ocean model.

In order to maintain bit reproducibility, all field initialization is
computed or assigned on the input grid.

This patch also includes an extension of the test suite, which applies a
single quarter-turn to each of the existing tests.

The features of this patch are summarized below.

Two new configuration features are added:

- ROTATE_INDEX: Boolean flag which enables this feature.
- INDEX_TURNS: Integer which descibes the number of counterclockwise
    quarter-turns.  Default value is 1.

Two new modules and several new functions are added:

- MOM_array_transform:  Fundamental operations for rotating arrays,
    vectors, rotationally equivalent pairs

- MOM_transform_FMS: Wrappers to FMS operations which can only operate
    on the unrotated index pax.

- Numerous rotation functions have been introduced in various modules
  associated with initialization (forcing, OBC, etc), which are
  documented in the source code.

The following new features have been added to existing components:

- paired checksums (`uvchksum`, `hchksum_pair`) now include a `scalar_pair`
  argument to distinguish between rotationally equivalent scalars (e.g.
  grid areas on u- and v-points) and vector components.

- `register_restart_pair` was introduced to register rotationally
  associated pairs.

- `turns` has been added to the horizontal index (HI) type, as a means
  to track the number of quarter turns in lower level operations.

- `allocate_forcing_type` and `allocate_mech_forcing` have been extended
  to permit allocation based on an exisiting (unrotated) reference,
  alongside the older interface using control flags for each field type.

- copy_MOM_domain includes a `turns` argument, to support rotational
  changes to the FMS domain.

Reproducibility:

- All results should be bitwise reproducible.  A nonphysical exception
  exception is the calculation of CFL conditions in systems with zero
  velocity, such as the unit test case.  The previous implementation
  could produce a "negative zero" CFL, whose sign would depend on the
  number of rotations.

  The new implementation eliminates these negative zeros, but may also
  change existing CFL calculations, and may report a regression in
  `ocean.stats`.

- Rotated diagnostic checksum output is not tested, since there is not
  yet a way to associated rotationally equivalent diagnostics.  See
  below for more information.

Current limitations:

- Only single PE runs can be rotated.

    Currently FMS assumes a specific ordering for assignment of PE IDs
    (east->west, south->north).  An index rotation will typically
    re-orient these domains in a way which breaks this default
    assignment.  This ID is used to manage MPI message passing
    targets, so any rearrangement of these IDs will cause errors.

    Since we currently cannot control the PE ID assigned by FMS, we are
    currently unable to support index rotation of parallel runs.

- Certain fields and tracers cannot be rotated.

    There are a small subset of tracers and fields in various
    structures, such as boundary fluxes, which are currently
    unsupported.  This is primarily beacuse these tracers are typically
    managed at the driver level.

    There is no reason why these fields cannot be supported, but it
    would require a larger test suite to detect and handle such fields.

- Diagnostic output is not rotated.

    Diagnostics are computed on the rotated grid, but it would require
    additional FMS wrappers to support registering and writing
    diagnostics on the input index map.  This is not yet implemented,
    so all diagnostics are on the rotated index map.

    One additional consequence is that there is no way to register pairs
    of diagnostics, and thus no way to automate the chksum.diag output.
    Currently, diagnostic checksums are disabled in the test suite.

    We hope to resolve this issue in a future patch.

- Open boundary condition (OBC) rotation is restriced to INDEX_TURNS=1

    Generalized OBC rotation is currently not supported.  This is
    primarily due to the sensitivity of segment configuration, which is
    specified with a large number of flags denoting its orientation.

    It is possible to support generalized rotation, but it will require
    a refactor of the OBC segment code.

    We hope to support generalized rotation in a future patch.

- Incorrect definitions of gridLonT and gridLatT

    Due to certain array length assumptions for 1d axes, we rotate and
    exchange the values of gridLatT and gridLonT during quarter-turn
    operations.

    This is not correct, since these arrays describe latitude and
    longitude, rather than the axes in the first and second dimension.
    However, several other components currently expect the first and
    second reference axis to have matching array lengths, so for now we
    swap these arrays during rotations.

    The 1d arrays are only used for initialization computation (which is
    on the input map) and I/O management, so this error does not affect
    any existing runs.  But rotated runs which save these arrays will be
    incorrect.

- Non-ALE sponge rotation is disabled.

    Only ALE sponge rotation is currently supported.

    It should be possible to extend rotation to include non-ALE sponges
    if needed.

Further comments:

- Fields are not rotated in-place, since many calculations require that
  both fields exist at any time.  While it is unlikely that rotated and
  unrotated fields would persist for an entire run, there is likely to
  be an increase in memory overhead.

- External fields from the drivers are rotated and unrotated at the
  beginning and end of every timestep, which can create additional
  computational overhead.

  Note that there are control flags, e.g. `cycle_start` and `cycle_end`
  which could be used to manage these operations, assuming that the
  driver does not change the fields inbetween coupler steps.

- This patch fixes what appears to have been an error in the global
  h-point indexes of OBC segments.  However, it also seems that these
  indices were unused, so this is unlikely to impact existing runs.

This implementation was proposed by Robert Hallberg and follows an
earlier implementation by Nic Hannah, whose efforts introduced several
changes that implemented the rotational invariance of existing numerical
calculations.
  • Loading branch information
marshallward committed Apr 14, 2020
1 parent 3e9c645 commit 22215cb
Show file tree
Hide file tree
Showing 29 changed files with 2,981 additions and 316 deletions.
12 changes: 10 additions & 2 deletions .testing/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ MKMF_TEMPLATE ?= build/mkmf/templates/ncrc-gnu.mk
# Executables
BUILDS = symmetric asymmetric repro openmp
CONFIGS := $(wildcard tc*)
TESTS = grids layouts restarts nans dims openmps
TESTS = grids layouts restarts nans dims openmps rotations

# REPRO tests enable reproducibility with optimization, and often do not match
# the DEBUG results in older GCCs and vendor compilers, so we can optionally
Expand Down Expand Up @@ -186,9 +186,15 @@ test: $(foreach t,$(TESTS),test.$(t))

# NOTE: We remove tc3 (OBC) from grid test since it cannot run asymmetric grids

# NOTE: rotation diag chksum disabled since we cannot yet compare rotationally
# equivalent diagnostics

# TODO: restart checksum comparison is not yet implemented

.PHONY: $(foreach t,$(TESTS),test.$(t))
test.grids: $(foreach c,$(filter-out tc3,$(CONFIGS)),$(c).grid $(c).grid.diag)
test.layouts: $(foreach c,$(CONFIGS),$(c).layout $(c).layout.diag)
test.rotations: $(foreach c,$(CONFIGS),$(c).rotate)
test.restarts: $(foreach c,$(CONFIGS),$(c).restart)
test.repros: $(foreach c,$(CONFIGS),$(c).repro $(c).repro.diag)
test.openmps: $(foreach c,$(CONFIGS),$(c).openmp $(c).openmp.diag)
Expand All @@ -210,6 +216,7 @@ endef

$(eval $(call CMP_RULE,grid,symmetric asymmetric))
$(eval $(call CMP_RULE,layout,symmetric layout))
$(eval $(call CMP_RULE,rotate,symmetric rotate))
$(eval $(call CMP_RULE,repro,symmetric repro))
$(eval $(call CMP_RULE,openmp,symmetric openmp))
$(eval $(call CMP_RULE,nan,symmetric nan))
Expand Down Expand Up @@ -260,7 +267,7 @@ results/%/ocean.stats.$(1): build/$(2)/MOM6
cp -rL $$*/* work/$$*/$(1)
cd work/$$*/$(1) && if [ -f Makefile ]; then make; fi
mkdir -p work/$$*/$(1)/RESTART
echo $(4) > work/$$*/$(1)/MOM_override
echo -e "$(4)" > work/$$*/$(1)/MOM_override
cd work/$$*/$(1) && $$(call MPIRUN_CMD,$(5)) -n $(6) ../../../$$< 2> debug.out > std.out \
|| ! sed 's/^/$$*.$(1): /' std.out debug.out \
&& sed 's/^/$$*.$(1): /' std.out
Expand All @@ -282,6 +289,7 @@ $(eval $(call STAT_RULE,target,target,,,,1))
$(eval $(call STAT_RULE,repro,repro,,,,1))
$(eval $(call STAT_RULE,openmp,openmp,,,,1))
$(eval $(call STAT_RULE,layout,symmetric,,LAYOUT=2$(,)1,,2))
$(eval $(call STAT_RULE,rotate,symmetric,,ROTATE_INDEX=True\nINDEX_TURNS=1,,1))
$(eval $(call STAT_RULE,nan,symmetric,,,MALLOC_PERTURB_=256,1))
$(eval $(call STAT_RULE,dim.t,symmetric,,T_RESCALE_POWER=11,,1))
$(eval $(call STAT_RULE,dim.l,symmetric,,L_RESCALE_POWER=11,,1))
Expand Down
1 change: 1 addition & 0 deletions .testing/tc2/MOM_input
Original file line number Diff line number Diff line change
Expand Up @@ -600,3 +600,4 @@ ENERGYSAVEDAYS = 0.5 ! [days] default = 3600.0
! energies of the run and other globally summed diagnostics.
DIAG_AS_CHKSUM = True
DEBUG = True
USE_GM_WORK_BUG = False
338 changes: 288 additions & 50 deletions src/core/MOM.F90

Large diffs are not rendered by default.

34 changes: 21 additions & 13 deletions src/core/MOM_barotropic.F90
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ module MOM_barotropic
use MOM_open_boundary, only : ocean_OBC_type, OBC_SIMPLE, OBC_NONE, open_boundary_query
use MOM_open_boundary, only : OBC_DIRECTION_E, OBC_DIRECTION_W
use MOM_open_boundary, only : OBC_DIRECTION_N, OBC_DIRECTION_S, OBC_segment_type
use MOM_restart, only : register_restart_field, query_initialized, MOM_restart_CS
use MOM_restart, only : register_restart_field, register_restart_pair
use MOM_restart, only : query_initialized, MOM_restart_CS
use MOM_tidal_forcing, only : tidal_forcing_sensitivity, tidal_forcing_CS
use MOM_time_manager, only : time_type, real_to_time, operator(+), operator(-)
use MOM_unit_scaling, only : unit_scale_type
Expand Down Expand Up @@ -1536,11 +1537,15 @@ subroutine btstep(U_in, V_in, eta_in, dt, bc_accel_u, bc_accel_v, forces, pbce,
if (.not. use_BT_cont) then
call uvchksum("BT Dat[uv]", Datu, Datv, CS%debug_BT_HI, haloshift=1, scale=US%L_to_m*GV%H_to_m)
endif
call uvchksum("BT wt_[uv]", wt_u, wt_v, G%HI, 0, .true., .true.)
call uvchksum("BT frhat[uv]", CS%frhatu, CS%frhatv, G%HI, 0, .true., .true.)
call uvchksum("BT wt_[uv]", wt_u, wt_v, G%HI, haloshift=0, &
symmetric=.true., omit_corners=.true., scalar_pair=.true.)
call uvchksum("BT frhat[uv]", CS%frhatu, CS%frhatv, G%HI, haloshift=0, &
symmetric=.true., omit_corners=.true., scalar_pair=.true.)
call uvchksum("BT bc_accel_[uv]", bc_accel_u, bc_accel_v, G%HI, haloshift=0, scale=US%L_T2_to_m_s2)
call uvchksum("BT IDat[uv]", CS%IDatu, CS%IDatv, G%HI, haloshift=0, scale=US%m_to_Z)
call uvchksum("BT visc_rem_[uv]", visc_rem_u, visc_rem_v, G%HI, haloshift=1)
call uvchksum("BT IDat[uv]", CS%IDatu, CS%IDatv, G%HI, haloshift=0, &
scale=US%m_to_Z, scalar_pair=.true.)
call uvchksum("BT visc_rem_[uv]", visc_rem_u, visc_rem_v, G%HI, &
haloshift=1, scalar_pair=.true.)
endif

if (query_averaging_enabled(CS%diag)) then
Expand Down Expand Up @@ -3113,9 +3118,13 @@ subroutine btcalc(h, G, GV, CS, h_u, h_v, may_use_default, OBC)
enddo ; endif

if (CS%debug) then
call uvchksum("btcalc frhat[uv]", CS%frhatu, CS%frhatv, G%HI, 0, .true., .true.)
call uvchksum("btcalc frhat[uv]", CS%frhatu, CS%frhatv, G%HI, &
haloshift=0, symmetric=.true., omit_corners=.true., &
scalar_pair=.true.)
if (present(h_u) .and. present(h_v)) &
call uvchksum("btcalc h_[uv]", h_u, h_v, G%HI, 0, .true., .true., scale=GV%H_to_m)
call uvchksum("btcalc h_[uv]", h_u, h_v, G%HI, haloshift=0, &
symmetric=.true., omit_corners=.true., scale=GV%H_to_m, &
scalar_pair=.true.)
call hchksum(h, "btcalc h",G%HI, haloshift=1, scale=GV%H_to_m)
endif

Expand Down Expand Up @@ -4235,6 +4244,7 @@ subroutine barotropic_init(u, v, h, eta, Time, G, GV, US, param_file, diag, CS,
CS%debug_BT_HI%IedB=CS%iedw
CS%debug_BT_HI%JsdB=CS%jsdw-1
CS%debug_BT_HI%JedB=CS%jedw
CS%debug_BT_HI%turns = G%HI%turns
endif

! IareaT, IdxCu, and IdyCv need to be allocated with wide halos.
Expand Down Expand Up @@ -4607,6 +4617,7 @@ subroutine register_barotropic_restarts(HI, GV, param_file, CS, restart_CS)
type(vardesc) :: vd(3)
real :: slow_rate
integer :: isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB

isd = HI%isd ; ied = HI%ied ; jsd = HI%jsd ; jed = HI%jed
IsdB = HI%IsdB ; IedB = HI%IedB ; JsdB = HI%JsdB ; JedB = HI%JedB

Expand All @@ -4628,17 +4639,15 @@ subroutine register_barotropic_restarts(HI, GV, param_file, CS, restart_CS)
hor_grid='u', z_grid='1')
vd(3) = var_desc("vbtav","m s-1","Time mean barotropic meridional velocity",&
hor_grid='v', z_grid='1')
call register_restart_field(CS%ubtav, vd(2), .false., restart_CS)
call register_restart_field(CS%vbtav, vd(3), .false., restart_CS)
call register_restart_pair(CS%ubtav, CS%vbtav, vd(2), vd(3), .false., restart_CS)

vd(2) = var_desc("ubt_IC", "m s-1", &
longname="Next initial condition for the barotropic zonal velocity", &
hor_grid='u', z_grid='1')
vd(3) = var_desc("vbt_IC", "m s-1", &
longname="Next initial condition for the barotropic meridional velocity",&
hor_grid='v', z_grid='1')
call register_restart_field(CS%ubt_IC, vd(2), .false., restart_CS)
call register_restart_field(CS%vbt_IC, vd(3), .false., restart_CS)
call register_restart_pair(CS%ubt_IC, CS%vbt_IC, vd(2), vd(3), .false., restart_CS)

if (GV%Boussinesq) then
vd(2) = var_desc("uhbt_IC", "m3 s-1", &
Expand All @@ -4655,8 +4664,7 @@ subroutine register_barotropic_restarts(HI, GV, param_file, CS, restart_CS)
longname="Next initial condition for the barotropic meridional transport",&
hor_grid='v', z_grid='1')
endif
call register_restart_field(CS%uhbt_IC, vd(2), .false., restart_CS)
call register_restart_field(CS%vhbt_IC, vd(3), .false., restart_CS)
call register_restart_pair(CS%uhbt_IC, CS%vhbt_IC, vd(2), vd(3), .false., restart_CS)

call register_restart_field(CS%dtbt, "DTBT", .false., restart_CS, &
longname="Barotropic timestep", units="seconds")
Expand Down
40 changes: 18 additions & 22 deletions src/core/MOM_dynamics_split_RK2.F90
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ module MOM_dynamics_split_RK2
use MOM_file_parser, only : get_param, log_version, param_file_type
use MOM_get_input, only : directories
use MOM_io, only : MOM_io_init, vardesc, var_desc
use MOM_restart, only : register_restart_field, query_initialized, save_restart
use MOM_restart, only : register_restart_field, register_restart_pair
use MOM_restart, only : query_initialized, save_restart
use MOM_restart, only : restart_init, is_new_run, MOM_restart_CS
use MOM_time_manager, only : time_type, time_type_to_real, operator(+)
use MOM_time_manager, only : operator(-), operator(>), operator(*), operator(/)
Expand Down Expand Up @@ -886,11 +887,12 @@ subroutine register_restarts_dyn_split_RK2(HI, GV, param_file, CS, restart_CS, u
real, dimension(SZI_(HI),SZJB_(HI),SZK_(GV)), &
target, intent(inout) :: vh !< merid volume/mass transport [H L2 T-1 ~> m3 s-1 or kg s-1]

type(vardesc) :: vd
type(vardesc) :: vd(2)
character(len=40) :: mdl = "MOM_dynamics_split_RK2" ! This module's name.
character(len=48) :: thickness_units, flux_units

integer :: isd, ied, jsd, jed, nz, IsdB, IedB, JsdB, JedB

isd = HI%isd ; ied = HI%ied ; jsd = HI%jsd ; jed = HI%jed ; nz = GV%ke
IsdB = HI%IsdB ; IedB = HI%IedB ; JsdB = HI%JsdB ; JedB = HI%JedB

Expand Down Expand Up @@ -918,32 +920,26 @@ subroutine register_restarts_dyn_split_RK2(HI, GV, param_file, CS, restart_CS, u
flux_units = get_flux_units(GV)

if (GV%Boussinesq) then
vd = var_desc("sfc",thickness_units,"Free surface Height",'h','1')
vd(1) = var_desc("sfc",thickness_units,"Free surface Height",'h','1')
else
vd = var_desc("p_bot",thickness_units,"Bottom Pressure",'h','1')
vd(1) = var_desc("p_bot",thickness_units,"Bottom Pressure",'h','1')
endif
call register_restart_field(CS%eta, vd, .false., restart_CS)

vd = var_desc("u2","m s-1","Auxiliary Zonal velocity",'u','L')
call register_restart_field(CS%u_av, vd, .false., restart_CS)

vd = var_desc("v2","m s-1","Auxiliary Meridional velocity",'v','L')
call register_restart_field(CS%v_av, vd, .false., restart_CS)

vd = var_desc("h2",thickness_units,"Auxiliary Layer Thickness",'h','L')
call register_restart_field(CS%h_av, vd, .false., restart_CS)
call register_restart_field(CS%eta, vd(1), .false., restart_CS)

vd = var_desc("uh",flux_units,"Zonal thickness flux",'u','L')
call register_restart_field(uh, vd, .false., restart_CS)
vd(1) = var_desc("u2","m s-1","Auxiliary Zonal velocity",'u','L')
vd(2) = var_desc("v2","m s-1","Auxiliary Meridional velocity",'v','L')
call register_restart_pair(CS%u_av, CS%v_av, vd(1), vd(2), .false., restart_CS)

vd = var_desc("vh",flux_units,"Meridional thickness flux",'v','L')
call register_restart_field(vh, vd, .false., restart_CS)
vd(1) = var_desc("h2",thickness_units,"Auxiliary Layer Thickness",'h','L')
call register_restart_field(CS%h_av, vd(1), .false., restart_CS)

vd = var_desc("diffu","m s-2","Zonal horizontal viscous acceleration",'u','L')
call register_restart_field(CS%diffu, vd, .false., restart_CS)
vd(1) = var_desc("uh",flux_units,"Zonal thickness flux",'u','L')
vd(2) = var_desc("vh",flux_units,"Meridional thickness flux",'v','L')
call register_restart_pair(uh, vh, vd(1), vd(2), .false., restart_CS)

vd = var_desc("diffv","m s-2","Meridional horizontal viscous acceleration",'v','L')
call register_restart_field(CS%diffv, vd, .false., restart_CS)
vd(1) = var_desc("diffu","m s-2","Zonal horizontal viscous acceleration",'u','L')
vd(2) = var_desc("diffv","m s-2","Meridional horizontal viscous acceleration",'v','L')
call register_restart_pair(CS%diffu, CS%diffv, vd(1), vd(2), .false., restart_CS)

call register_barotropic_restarts(HI, GV, param_file, CS%barotropic_CSp, &
restart_CS)
Expand Down
Loading

0 comments on commit 22215cb

Please sign in to comment.