From cf1236986d0e811e545a7867684c0cda00872ce4 Mon Sep 17 00:00:00 2001 From: Michael Levy Date: Thu, 8 Oct 2015 14:28:49 -0600 Subject: [PATCH 01/61] Bug fix for compare_namelist.pl Old version didn't give correct results if you add variables to the namelist --- scripts/Tools/compare_namelists.pl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/Tools/compare_namelists.pl b/scripts/Tools/compare_namelists.pl index 56ed7c1f610..b1fbc16ec5d 100755 --- a/scripts/Tools/compare_namelists.pl +++ b/scripts/Tools/compare_namelists.pl @@ -23,13 +23,17 @@ my $cnt2=$#basenml; my $shiftbl=1; foreach $line (@nlfile){ + chomp $line; my $bline = shift(@basenml) if($shiftbl); + chomp $bline; $line =~ s/\s+/ /g; $bline =~ s/\s+/ /g; $shiftbl=1; next if($line eq $bline); $bline=shift(@basenml) if(($bline =~ /^\s*[#!\[\/]/) or ($bline =~ /^\s*$/)); + chomp $bline; + next if($line eq $bline); next if(defined $baseid && $line =~ /$baseid/); next if($line =~ /^\s*[#!\[\/]/); next if($line =~ /^\s*$/); @@ -44,8 +48,6 @@ if($bline =~ /(.*)=(.*)/){ $name2 = $1; } - chomp $line; - chomp $bline; if($name1 eq $name2){ push(@diffs1,$line); push(@diffs2,$bline); @@ -53,6 +55,7 @@ push(@added,$line); $shiftbl=0; $cnt1--; + unshift(@basenml, $bline); }elsif($cnt2>$cnt1){ push(@removed,$bline); shift(@basenml); From 3a3ac1c34105754c902f2e4395f54bb9c0385e40 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 20 Apr 2016 11:21:34 -0600 Subject: [PATCH 02/61] Add a routine to glc_elevclass_mod: glc_all_elevclass_strings Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- driver_cpl/shr/glc_elevclass_mod.F90 | 28 +++++++++++++++++++ .../glc_elevclass_test/test_glc_elevclass.pf | 18 ++++++++++++ 2 files changed, 46 insertions(+) diff --git a/driver_cpl/shr/glc_elevclass_mod.F90 b/driver_cpl/shr/glc_elevclass_mod.F90 index 0bf2c3fb399..f9dca015f69 100644 --- a/driver_cpl/shr/glc_elevclass_mod.F90 +++ b/driver_cpl/shr/glc_elevclass_mod.F90 @@ -26,6 +26,7 @@ module glc_elevclass_mod public :: glc_get_elevation_class ! get the elevation class index for a given elevation public :: glc_mean_elevation_virtual ! get the mean elevation of a virtual elevation class public :: glc_elevclass_as_string ! returns a string corresponding to a given elevation class + public :: glc_all_elevclass_strings ! returns an array of strings for all elevation classes public :: glc_errcode_to_string ! convert an error code into a string describing the error interface glc_elevclass_init @@ -309,6 +310,33 @@ function glc_elevclass_as_string(elevation_class) result(ec_string) write(ec_string,'(i2.2)') elevation_class end function glc_elevclass_as_string + !----------------------------------------------------------------------- + function glc_all_elevclass_strings() result(ec_strings) + ! + ! !DESCRIPTION: + ! Returns an array of strings corresponding to all elevation classes from 1 to glc_nec + ! + ! These strings can be used as suffixes for fields in MCT attribute vectors. + ! + ! !USES: + ! + ! !ARGUMENTS: + character(len=2), allocatable :: ec_strings(:) ! function result + ! + ! !LOCAL VARIABLES: + integer :: i + + character(len=*), parameter :: subname = 'glc_all_elevclass_strings' + !----------------------------------------------------------------------- + + allocate(ec_strings(1:glc_nec)) + do i = 1, glc_nec + ec_strings(i) = glc_elevclass_as_string(i) + end do + + end function glc_all_elevclass_strings + + !----------------------------------------------------------------------- function glc_errcode_to_string(err_code) result(err_string) ! diff --git a/driver_cpl/unit_test/glc_elevclass_test/test_glc_elevclass.pf b/driver_cpl/unit_test/glc_elevclass_test/test_glc_elevclass.pf index 83a0bc44255..2459e6c33cb 100644 --- a/driver_cpl/unit_test/glc_elevclass_test/test_glc_elevclass.pf +++ b/driver_cpl/unit_test/glc_elevclass_test/test_glc_elevclass.pf @@ -246,5 +246,23 @@ contains str = glc_elevclass_as_string(12) @assertEqual('12', trim(str)) end subroutine test_glc_elevclass_as_string_2digits + + ! ------------------------------------------------------------------------ + ! Tests of glc_all_elevclass_strings + ! ------------------------------------------------------------------------ + + @Test + subroutine test_glc_all_elevclass_strings(this) + class(TestGLCElevclass), intent(inout) :: this + character(len=16) :: elevclass_strings(3) + + call glc_elevclass_init(3) + elevclass_strings = glc_all_elevclass_strings() + + ! There doesn't seem to be an assertEqual method for an array of strings + @assertEqual('01', elevclass_strings(1)) + @assertEqual('02', elevclass_strings(2)) + @assertEqual('03', elevclass_strings(3)) + end subroutine test_glc_all_elevclass_strings end module test_glc_elevclass From bf715bb6d7fa8a5f3fc3834fa8e3c35d40f03900 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 20 Apr 2016 11:53:20 -0600 Subject: [PATCH 03/61] Remove dependence of vertical_gradient_calculator on glc_elevclass_mod Make the vertical gradient calculator agnostic of the fact that it's working with glc elevation classes, by passing in the elevation class names. Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- driver_cpl/driver/prep_glc_mod.F90 | 5 +-- ...vertical_gradient_calculator_2nd_order.F90 | 23 +++++++------- ..._vertical_gradient_calculator_2nd_order.pf | 31 +++++++++++++------ 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/driver_cpl/driver/prep_glc_mod.F90 b/driver_cpl/driver/prep_glc_mod.F90 index 60cbdf3730d..42631a359ea 100644 --- a/driver_cpl/driver/prep_glc_mod.F90 +++ b/driver_cpl/driver/prep_glc_mod.F90 @@ -407,7 +407,7 @@ subroutine prep_glc_map_one_field_lnd2glc(egi, eli, fieldname, fractions_lx, map ! vertical gradient calculator. use vertical_gradient_calculator_2nd_order, only : vertical_gradient_calculator_2nd_order_type - use glc_elevclass_mod, only : glc_get_num_elevation_classes + use glc_elevclass_mod, only : glc_get_num_elevation_classes, glc_all_elevclass_strings use map_lnd2glc_mod, only : map_lnd2glc ! Arguments @@ -429,7 +429,8 @@ subroutine prep_glc_map_one_field_lnd2glc(egi, eli, fieldname, fractions_lx, map fieldname = fieldname, & toponame = 'Sl_topo', & min_elevation_class = 1, & - max_elevation_class = glc_get_num_elevation_classes()) + max_elevation_class = glc_get_num_elevation_classes(), & + elevclass_names = glc_all_elevclass_strings()) call map_lnd2glc(l2x_l = l2gacc_lx(eli), & landfrac_l = fractions_lx, & g2x_g = g2x_gx, & diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index 2f63ca1f6a5..7615602ff81 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -6,14 +6,11 @@ module vertical_gradient_calculator_2nd_order ! ! This module defines a subclass of vertical_gradient_calculator_base_type for ! computing vertical gradients using a second-order centered difference. - ! - ! Currently, this code assumes that it is working with GLC elevation classes. #include "shr_assert.h" use seq_comm_mct, only : logunit use vertical_gradient_calculator_base, only : vertical_gradient_calculator_base_type use shr_kind_mod, only : r8 => shr_kind_r8 - use glc_elevclass_mod, only : glc_elevclass_as_string use mct_mod use shr_log_mod, only : errMsg => shr_log_errMsg use shr_sys_mod, only : shr_sys_abort @@ -48,13 +45,16 @@ module vertical_gradient_calculator_2nd_order !----------------------------------------------------------------------- function constructor(attr_vect, fieldname, toponame, & - min_elevation_class, max_elevation_class) & + min_elevation_class, max_elevation_class, elevclass_names) & result(this) ! ! !DESCRIPTION: ! Creates a vertical_gradient_calculator_2nd_order_type object by reading the ! necessary data from the provided attribute vector ! + ! The attribute vector is assumed to have fields named fieldname // + ! elevclass_names(1), toponame // elevclass_names(1), etc. + ! ! !USES: ! ! !ARGUMENTS: @@ -64,15 +64,18 @@ function constructor(attr_vect, fieldname, toponame, & character(len=*) , intent(in) :: toponame ! base name of the topographic field integer , intent(in) :: min_elevation_class ! first elevation class index integer , intent(in) :: max_elevation_class ! last elevation class index + character(len=*) , intent(in) :: elevclass_names( min_elevation_class: ) ! strings corresponding to each elevation class ! ! !LOCAL VARIABLES: character(len=*), parameter :: subname = 'constructor' !----------------------------------------------------------------------- + SHR_ASSERT_ALL((ubound(elevclass_names) == (/max_elevation_class/)), errMsg(__FILE__, __LINE__)) + this%min_elevation_class = min_elevation_class this%max_elevation_class = max_elevation_class - call this%set_data_from_attr_vect(attr_vect, fieldname, toponame) + call this%set_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names) end function constructor @@ -150,7 +153,7 @@ end subroutine calc_vertical_gradient !----------------------------------------------------------------------- - subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame) + subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame, elevclass_names) ! ! !DESCRIPTION: ! Extract data from an attribute vector. @@ -165,10 +168,10 @@ subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame) type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data character(len=*) , intent(in) :: fieldname ! base name of the field of interest character(len=*) , intent(in) :: toponame ! base name of the topographic field + character(len=*) , intent(in) :: elevclass_names( this%min_elevation_class: ) ! strings corresponding to each elevation class ! ! !LOCAL VARIABLES: integer :: elevclass - character(len=:), allocatable :: elevclass_as_string character(len=:), allocatable :: fieldname_ec character(len=:), allocatable :: toponame_ec @@ -185,13 +188,11 @@ subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame) allocate(temp(this%num_points)) do elevclass = this%min_elevation_class, this%max_elevation_class - elevclass_as_string = glc_elevclass_as_string(elevclass) - fieldname_ec = trim(fieldname) // elevclass_as_string + fieldname_ec = trim(fieldname) // trim(elevclass_names(elevclass)) call mct_aVect_exportRattr(attr_vect, fieldname_ec, temp) this%field(:,elevclass) = temp(:) - elevclass_as_string = glc_elevclass_as_string(elevclass) - toponame_ec = trim(toponame) // elevclass_as_string + toponame_ec = trim(toponame) // trim(elevclass_names(elevclass)) call mct_aVect_exportRattr(attr_vect, toponame_ec, temp) this%topo(:,elevclass) = temp(:) end do diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index cbfad87391a..8c842afa31f 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -9,7 +9,6 @@ module test_vertical_gradient_calculator_2nd_order use mct_mod, only : mct_aVect, mct_aVect_clean use mct_wrapper_mod, only : mct_init, mct_clean use avect_wrapper_mod - use glc_elevclass_mod, only : glc_elevclass_as_string implicit none @@ -41,6 +40,14 @@ contains call mct_clean() end subroutine tearDown + function two_digit_string(val) + ! Converts val to a two-digit string + character(len=2) :: two_digit_string + integer, intent(in) :: val + + write(two_digit_string, '(i2.2)') val + end function two_digit_string + subroutine create_av(this, topo, data, toponame, dataname) ! Creates the attribute vector in 'this' class(TestVertGradCalc2ndOrder), intent(inout) :: this @@ -61,10 +68,10 @@ contains allocate(attr_tags(2*n_elev_classes)) do elevclass = 1, n_elev_classes - attr_tags(elevclass) = dataname // glc_elevclass_as_string(elevclass) + attr_tags(elevclass) = dataname // two_digit_string(elevclass) end do do elevclass = 1, n_elev_classes - attr_tags(n_elev_classes + elevclass) = toponame // glc_elevclass_as_string(elevclass) + attr_tags(n_elev_classes + elevclass) = toponame // two_digit_string(elevclass) end do call create_aVect_with_data_rows_are_points(this%av, & @@ -81,17 +88,23 @@ contains real(r8), intent(in) :: data(:,:) ! data(i,j) is point i, elevation class j integer :: n_elev_classes - character(len=16), allocatable :: attr_tags(:) + character(len=16), allocatable :: elevclass_names(:) + integer :: i call this%create_av(topo, data, 'topo', 'data') n_elev_classes = size(data,2) + allocate(elevclass_names(1:n_elev_classes)) + do i = 1, n_elev_classes + elevclass_names(i) = two_digit_string(i) + end do calculator = vertical_gradient_calculator_2nd_order_type( & attr_vect = this%av, & fieldname = 'data', & toponame = 'topo', & min_elevation_class = 1, & - max_elevation_class = n_elev_classes) + max_elevation_class = n_elev_classes, & + elevclass_names = elevclass_names) end function create_calculator @@ -108,12 +121,12 @@ contains [11._r8, 12._r8, 13._r8, & 14._r8, 15._r8, 16._r8], & [3, 2]) - + call this%create_av(topo, data, 'topo', 'data') - @assertEqual([4._r8, 5._r8, 6._r8], aVect_exportRattr(this%av, 'topo' // glc_elevclass_as_string(2))) + @assertEqual([4._r8, 5._r8, 6._r8], aVect_exportRattr(this%av, 'topo' // two_digit_string(2))) - @assertEqual([14._r8, 15._r8, 16._r8], aVect_exportRattr(this%av, 'data' // glc_elevclass_as_string(2))) + @assertEqual([14._r8, 15._r8, 16._r8], aVect_exportRattr(this%av, 'data' // two_digit_string(2))) end subroutine test_create_av @@ -134,7 +147,7 @@ contains expected_vertical_gradient(1) = (data(1,3) - data(1,1)) / (topo(1,3) - topo(1,1)) @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) - + end subroutine test_calc_vertical_gradient_ECmid @Test From 823b635287ab3289320f8815fde6e71f7f56452f Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 20 Apr 2016 15:23:10 -0600 Subject: [PATCH 04/61] Add elevation class bounds to vertical gradient calculator This will be needed for the limiter Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...vertical_gradient_calculator_2nd_order.F90 | 20 +++++++++- ..._vertical_gradient_calculator_2nd_order.pf | 39 ++++++++++++++----- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index 7615602ff81..e5eeec1c581 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -30,6 +30,11 @@ module vertical_gradient_calculator_2nd_order real(r8), allocatable :: field(:,:) ! field(i,j) is point i, elevation class j real(r8), allocatable :: topo(:,:) ! topo(i,j) is point i, elevation class j + ! Bounds of each elevation class. This array has one more element than the number of + ! elevation classes, since it contains lower and upper bounds for each elevation + ! class. The indices go (min_elevation_class-1):max_elevation_class + real(r8), allocatable :: elevclass_bounds(:) + contains procedure :: calc_vertical_gradient @@ -45,7 +50,8 @@ module vertical_gradient_calculator_2nd_order !----------------------------------------------------------------------- function constructor(attr_vect, fieldname, toponame, & - min_elevation_class, max_elevation_class, elevclass_names) & + min_elevation_class, max_elevation_class, elevclass_names, & + elevclass_bounds) & result(this) ! ! !DESCRIPTION: @@ -64,7 +70,14 @@ function constructor(attr_vect, fieldname, toponame, & character(len=*) , intent(in) :: toponame ! base name of the topographic field integer , intent(in) :: min_elevation_class ! first elevation class index integer , intent(in) :: max_elevation_class ! last elevation class index - character(len=*) , intent(in) :: elevclass_names( min_elevation_class: ) ! strings corresponding to each elevation class + + ! strings corresponding to each elevation class + character(len=*) , intent(in) :: elevclass_names( min_elevation_class: ) + + ! bounds of each elevation class; this array should have one more element than the + ! number of elevation classes, since it contains lower and upper bounds for each + ! elevation class + real(r8) , intent(in) :: elevclass_bounds( min_elevation_class-1 : ) ! ! !LOCAL VARIABLES: @@ -72,9 +85,12 @@ function constructor(attr_vect, fieldname, toponame, & !----------------------------------------------------------------------- SHR_ASSERT_ALL((ubound(elevclass_names) == (/max_elevation_class/)), errMsg(__FILE__, __LINE__)) + SHR_ASSERT_ALL((ubound(elevclass_bounds) == (/max_elevation_class/)), errMsg(__FILE__, __LINE__)) this%min_elevation_class = min_elevation_class this%max_elevation_class = max_elevation_class + allocate(this%elevclass_bounds((min_elevation_class-1):max_elevation_class)) + this%elevclass_bounds(:) = elevclass_bounds(:) call this%set_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names) end function constructor diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index 8c842afa31f..da9f24707e6 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -81,22 +81,30 @@ contains end subroutine create_av - function create_calculator(this, topo, data) result(calculator) + function create_calculator(this, topo, data, elevclass_bounds) & + result(calculator) type(vertical_gradient_calculator_2nd_order_type) :: calculator class(TestVertGradCalc2ndOrder), intent(inout) :: this real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j real(r8), intent(in) :: data(:,:) ! data(i,j) is point i, elevation class j + ! bounds of each elevation class; this array should have one more element than the + ! number of elevation classes, since it contains lower and upper bounds for each + ! elevation class + real(r8), intent(in) :: elevclass_bounds(:) + integer :: n_elev_classes character(len=16), allocatable :: elevclass_names(:) integer :: i - call this%create_av(topo, data, 'topo', 'data') n_elev_classes = size(data,2) + @assertEqual(n_elev_classes + 1, size(elevclass_bounds)) allocate(elevclass_names(1:n_elev_classes)) do i = 1, n_elev_classes elevclass_names(i) = two_digit_string(i) end do + + call this%create_av(topo, data, 'topo', 'data') calculator = vertical_gradient_calculator_2nd_order_type( & attr_vect = this%av, & @@ -104,7 +112,8 @@ contains toponame = 'topo', & min_elevation_class = 1, & max_elevation_class = n_elev_classes, & - elevclass_names = elevclass_names) + elevclass_names = elevclass_names, & + elevclass_bounds = elevclass_bounds) end function create_calculator @@ -136,12 +145,14 @@ contains ! (standard case, not an edge case). This uses a single grid cell. class(TestVertGradCalc2ndOrder), intent(inout) :: this type(vertical_gradient_calculator_2nd_order_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] real(r8), parameter :: topo(1,3) = reshape([50._r8, 125._r8, 275._r8], [1,3]) real(r8), parameter :: data(1,3) = reshape([11._r8, 12._r8, 13._r8], [1,3]) real(r8) :: vertical_gradient(1) real(r8) :: expected_vertical_gradient(1) - calculator = this%create_calculator(topo=topo, data=data) + calculator = this%create_calculator(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) call calculator%calc_vertical_gradient(2, vertical_gradient) @@ -156,12 +167,14 @@ contains ! single grid cell. class(TestVertGradCalc2ndOrder), intent(inout) :: this type(vertical_gradient_calculator_2nd_order_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] real(r8), parameter :: topo(1,3) = reshape([50._r8, 125._r8, 275._r8], [1,3]) real(r8), parameter :: data(1,3) = reshape([11._r8, 12._r8, 13._r8], [1,3]) real(r8) :: vertical_gradient(1) real(r8) :: expected_vertical_gradient(1) - calculator = this%create_calculator(topo=topo, data=data) + calculator = this%create_calculator(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) call calculator%calc_vertical_gradient(1, vertical_gradient) @@ -176,12 +189,14 @@ contains ! single grid cell. class(TestVertGradCalc2ndOrder), intent(inout) :: this type(vertical_gradient_calculator_2nd_order_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] real(r8), parameter :: topo(1,3) = reshape([50._r8, 125._r8, 275._r8], [1,3]) real(r8), parameter :: data(1,3) = reshape([11._r8, 12._r8, 13._r8], [1,3]) real(r8) :: vertical_gradient(1) real(r8) :: expected_vertical_gradient(1) - calculator = this%create_calculator(topo=topo, data=data) + calculator = this%create_calculator(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) call calculator%calc_vertical_gradient(3, vertical_gradient) @@ -196,12 +211,14 @@ contains ! cell. class(TestVertGradCalc2ndOrder), intent(inout) :: this type(vertical_gradient_calculator_2nd_order_type) :: calculator + real(r8), parameter :: elevclass_bounds(2) = [0._r8, 100._r8] real(r8), parameter :: topo(1,1) = reshape([50._r8], [1,1]) real(r8), parameter :: data(1,1) = reshape([11._r8], [1,1]) real(r8) :: vertical_gradient(1) real(r8) :: expected_vertical_gradient(1) - calculator = this%create_calculator(topo=topo, data=data) + calculator = this%create_calculator(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) call calculator%calc_vertical_gradient(1, vertical_gradient) @@ -216,12 +233,14 @@ contains ! handled correctly. class(TestVertGradCalc2ndOrder), intent(inout) :: this type(vertical_gradient_calculator_2nd_order_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] real(r8), parameter :: topo(1,3) = reshape([50._r8, 100._r8, 50._r8], [1,3]) real(r8), parameter :: data(1,3) = reshape([11._r8, 12._r8, 13._r8], [1,3]) real(r8) :: vertical_gradient(1) real(r8) :: expected_vertical_gradient(1) - calculator = this%create_calculator(topo=topo, data=data) + calculator = this%create_calculator(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) call calculator%calc_vertical_gradient(2, vertical_gradient) @@ -239,6 +258,7 @@ contains integer, parameter :: npts = 3 integer, parameter :: nelev = 3 + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] ! In the following, each line is one elevation class (with all points for that ! elevation class) real(r8), parameter :: topo(npts,nelev) = reshape( & @@ -255,7 +275,8 @@ contains real(r8) :: vertical_gradient(npts) real(r8) :: expected_vertical_gradient(npts) - calculator = this%create_calculator(topo=topo, data=data) + calculator = this%create_calculator(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) call calculator%calc_vertical_gradient(2, vertical_gradient) From a24c07e5879ebbb4c1122699323b83d51b8d5b89 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 21 Apr 2016 05:52:13 -0600 Subject: [PATCH 05/61] Finish changes for adding elevation class bounds to gradient calculator - Add a check that topographic heights are within the elevation class bounds. This is not strictly necessary, but without this, the limiter that we'll soon add could give bizarre results. - Changes to some unit tests in order to satisfy the new check, which ensures that topographic heights are within the bounds of each elevation class. - Pass elevation class bounds from production code Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- driver_cpl/driver/prep_glc_mod.F90 | 6 +- ...vertical_gradient_calculator_2nd_order.F90 | 70 ++++++++++++++++++- driver_cpl/shr/glc_elevclass_mod.F90 | 25 +++++++ ..._vertical_gradient_calculator_2nd_order.pf | 22 +++--- 4 files changed, 107 insertions(+), 16 deletions(-) diff --git a/driver_cpl/driver/prep_glc_mod.F90 b/driver_cpl/driver/prep_glc_mod.F90 index 42631a359ea..d0a3eae5487 100644 --- a/driver_cpl/driver/prep_glc_mod.F90 +++ b/driver_cpl/driver/prep_glc_mod.F90 @@ -407,7 +407,8 @@ subroutine prep_glc_map_one_field_lnd2glc(egi, eli, fieldname, fractions_lx, map ! vertical gradient calculator. use vertical_gradient_calculator_2nd_order, only : vertical_gradient_calculator_2nd_order_type - use glc_elevclass_mod, only : glc_get_num_elevation_classes, glc_all_elevclass_strings + use glc_elevclass_mod, only : glc_get_num_elevation_classes, & + glc_get_elevclass_bounds, glc_all_elevclass_strings use map_lnd2glc_mod, only : map_lnd2glc ! Arguments @@ -430,7 +431,8 @@ subroutine prep_glc_map_one_field_lnd2glc(egi, eli, fieldname, fractions_lx, map toponame = 'Sl_topo', & min_elevation_class = 1, & max_elevation_class = glc_get_num_elevation_classes(), & - elevclass_names = glc_all_elevclass_strings()) + elevclass_names = glc_all_elevclass_strings(), & + elevclass_bounds = glc_get_elevclass_bounds()) call map_lnd2glc(l2x_l = l2gacc_lx(eli), & landfrac_l = fractions_lx, & g2x_g = g2x_gx, & diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index e5eeec1c581..dcf8dbbea26 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -39,6 +39,7 @@ module vertical_gradient_calculator_2nd_order procedure :: calc_vertical_gradient procedure, private :: set_data_from_attr_vect ! extract data from an attribute vector + procedure, private :: check_topo ! check topographic heights end type vertical_gradient_calculator_2nd_order_type @@ -56,7 +57,15 @@ function constructor(attr_vect, fieldname, toponame, & ! ! !DESCRIPTION: ! Creates a vertical_gradient_calculator_2nd_order_type object by reading the - ! necessary data from the provided attribute vector + ! necessary data from the provided attribute vector. + ! + ! Pre-condition: Topographic heights in the attribute vector must all lie inside the + ! bounds of their respective elevation class (given by elevclass_bounds), with the + ! possible exception of the lowest elevation class (topographic heights can lie below + ! the arbitrary lower bound of the elevation class) and the highest elevation class + ! (topographic heights can lie above the arbitrary upper bound of the elevation + ! class). (This pre-condition is mainly important for the sake of calculating the + ! limiter.) ! ! The attribute vector is assumed to have fields named fieldname // ! elevclass_names(1), toponame // elevclass_names(1), etc. @@ -92,7 +101,9 @@ function constructor(attr_vect, fieldname, toponame, & allocate(this%elevclass_bounds((min_elevation_class-1):max_elevation_class)) this%elevclass_bounds(:) = elevclass_bounds(:) call this%set_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names) - + + call this%check_topo() + end function constructor @@ -217,5 +228,60 @@ subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame, elevcla end subroutine set_data_from_attr_vect + !----------------------------------------------------------------------- + subroutine check_topo(this) + ! + ! !DESCRIPTION: + ! Check topographic heights; abort if there is a problem + ! + ! Topographic heights in the attribute vector must all lie inside the bounds of their + ! respective elevation class (given by elevclass_bounds), with the possible exception + ! of the lowest elevation class (topographic heights can lie below the arbitrary lower + ! bound of the elevation class) and the highest elevation class (topographic heights + ! can lie above the arbitrary upper bound of the elevation class) + ! + ! !USES: + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_2nd_order_type), intent(in) :: this + ! + ! !LOCAL VARIABLES: + integer :: elevclass + integer :: i + + ! Absolute tolerance for error checks. This is chosen so that it allows for + ! double-precision roundoff-level errors on values of order 10,000. + real(r8), parameter :: tol = 1.e-10_r8 + + character(len=*), parameter :: subname = 'check_topo' + !----------------------------------------------------------------------- + + do elevclass = this%min_elevation_class, this%max_elevation_class + if (elevclass > this%min_elevation_class) then + do i = 1, this%num_points + if (this%topo(i,elevclass) - this%elevclass_bounds(elevclass-1) < -tol) then + write(logunit,*) subname, ': ERROR: topo lower than lower bound of elevation class:' + write(logunit,*) 'i, elevclass, topo, lower_bound = ', & + i, elevclass, this%topo(i,elevclass), this%elevclass_bounds(elevclass-1) + call shr_sys_abort(subname//': ERROR: topo lower than lower bound of elevation class') + end if + end do + end if + + if (elevclass < this%max_elevation_class) then + do i = 1, this%num_points + if (this%topo(i,elevclass) - this%elevclass_bounds(elevclass) > tol) then + write(logunit,*) subname, ': ERROR: topo higher than upper bound of elevation class:' + write(logunit,*) 'i, elevclass, topo, upper_bound = ', & + i, elevclass, this%topo(i,elevclass), this%elevclass_bounds(elevclass) + call shr_sys_abort(subname//': ERROR: topo higher than upper bound of elevation class') + end if + end do + end if + end do + + end subroutine check_topo + + end module vertical_gradient_calculator_2nd_order diff --git a/driver_cpl/shr/glc_elevclass_mod.F90 b/driver_cpl/shr/glc_elevclass_mod.F90 index f9dca015f69..e11b4242bbc 100644 --- a/driver_cpl/shr/glc_elevclass_mod.F90 +++ b/driver_cpl/shr/glc_elevclass_mod.F90 @@ -24,6 +24,7 @@ module glc_elevclass_mod public :: glc_elevclass_clean ! deallocate memory allocated here public :: glc_get_num_elevation_classes ! get the number of elevation classes public :: glc_get_elevation_class ! get the elevation class index for a given elevation + public :: glc_get_elevclass_bounds ! get the boundaries of all elevation classes public :: glc_mean_elevation_virtual ! get the mean elevation of a virtual elevation class public :: glc_elevclass_as_string ! returns a string corresponding to a given elevation class public :: glc_all_elevclass_strings ! returns an array of strings for all elevation classes @@ -223,6 +224,30 @@ subroutine glc_get_elevation_class(topo, elevation_class, err_code) end subroutine glc_get_elevation_class + !----------------------------------------------------------------------- + function glc_get_elevclass_bounds() result(elevclass_bounds) + ! + ! !DESCRIPTION: + ! Get the boundaries of all elevation classes. + ! + ! This returns an array of size glc_nec+1, since it contains both the lower and upper + ! bounds of each elevation class. + ! + ! !USES: + ! + ! !ARGUMENTS: + real(r8) :: elevclass_bounds(0:glc_nec) ! function result + ! + ! !LOCAL VARIABLES: + + character(len=*), parameter :: subname = 'glc_get_elevclass_bounds' + !----------------------------------------------------------------------- + + elevclass_bounds(:) = topomax(:) + + end function glc_get_elevclass_bounds + + !----------------------------------------------------------------------- function glc_mean_elevation_virtual(elevation_class) result(mean_elevation) ! diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index da9f24707e6..7ec04078483 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -233,9 +233,9 @@ contains ! handled correctly. class(TestVertGradCalc2ndOrder), intent(inout) :: this type(vertical_gradient_calculator_2nd_order_type) :: calculator - real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] - real(r8), parameter :: topo(1,3) = reshape([50._r8, 100._r8, 50._r8], [1,3]) - real(r8), parameter :: data(1,3) = reshape([11._r8, 12._r8, 13._r8], [1,3]) + real(r8), parameter :: elevclass_bounds(3) = [0._r8, 100._r8, 200._r8] + real(r8), parameter :: topo(1,2) = reshape([100._r8, 100._r8], [1,2]) + real(r8), parameter :: data(1,2) = reshape([11._r8, 12._r8], [1,2]) real(r8) :: vertical_gradient(1) real(r8) :: expected_vertical_gradient(1) @@ -257,19 +257,17 @@ contains type(vertical_gradient_calculator_2nd_order_type) :: calculator integer, parameter :: npts = 3 - integer, parameter :: nelev = 3 - real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + integer, parameter :: nelev = 2 + real(r8), parameter :: elevclass_bounds(3) = [0._r8, 100._r8, 200._r8] ! In the following, each line is one elevation class (with all points for that ! elevation class) real(r8), parameter :: topo(npts,nelev) = reshape( & - [50._r8, 5000._r8, 100._r8, & - 125._r8, 5000._r8, 101._r8, & - 275._r8, 5000._r8, 102._r8],& + [50._r8, 100._r8, 99._r8, & + 125._r8, 100._r8, 101._r8], & [npts,nelev]) real(r8), parameter :: data(npts,nelev) = reshape( & [11._r8, 100._r8, 1000._r8, & - 12._r8, 200._r8, 2000._r8, & - 13._r8, 300._r8, 3000._r8], & + 12._r8, 200._r8, 2000._r8], & [npts,nelev]) real(r8) :: vertical_gradient(npts) @@ -280,9 +278,9 @@ contains call calculator%calc_vertical_gradient(2, vertical_gradient) - expected_vertical_gradient(1) = (data(1,3) - data(1,1)) / (topo(1,3) - topo(1,1)) + expected_vertical_gradient(1) = (data(1,2) - data(1,1)) / (topo(1,2) - topo(1,1)) expected_vertical_gradient(2) = 0._r8 - expected_vertical_gradient(3) = (data(3,3) - data(3,1)) / (topo(3,3) - topo(3,1)) + expected_vertical_gradient(3) = (data(3,2) - data(3,1)) / (topo(3,2) - topo(3,1)) @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) end subroutine test_calc_vertical_gradient_multiplePoints From 4538e74f6951f68568ce7020526034300e0b240c Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 21 Apr 2016 06:02:29 -0600 Subject: [PATCH 06/61] Refactor: change where statement to loop with conditional This will make it easier to add the limiter Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- .../vertical_gradient_calculator_2nd_order.F90 | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index dcf8dbbea26..6f1c7588a06 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -131,6 +131,7 @@ subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) ! Tolerance for considering two topo values to be nearly equal real(r8), parameter :: topo_equality_tolerance = 1.e-13_r8 + integer :: i integer :: ec_low ! elevation class index to use as the lower bound of the gradient integer :: ec_high ! elevation class index to use as the upper bound of the gradient @@ -166,13 +167,15 @@ subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) ec_high = elevation_class + 1 end if - where(abs(this%topo(:, ec_high) - this%topo(:, ec_low)) < topo_equality_tolerance) - vertical_gradient = 0._r8 - elsewhere - vertical_gradient = & - (this%field(:, ec_high) - this%field(:, ec_low)) / & - (this%topo (:, ec_high) - this%topo (:, ec_low)) - end where + do i = 1, this%num_points + if (abs(this%topo(i, ec_high) - this%topo(i, ec_low)) < topo_equality_tolerance) then + vertical_gradient(i) = 0._r8 + else + vertical_gradient(i) = & + (this%field(i, ec_high) - this%field(i, ec_low)) / & + (this%topo (i, ec_high) - this%topo (i, ec_low)) + end if + end do end if From 6f692a85c12c009993e2c5cc44969d5320cf4d2b Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 21 Apr 2016 12:03:22 -0600 Subject: [PATCH 07/61] Make a parameterized test method I'm about to add a bunch of uses of this Test suite: None Test baseline: N/A Test namelist changes: N/A Test status: N/A Fixes: None User interface changes?: No Code review: None --- ..._vertical_gradient_calculator_2nd_order.pf | 60 ++++++++++++++----- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index 7ec04078483..d531f0f0546 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -22,6 +22,7 @@ module test_vertical_gradient_calculator_2nd_order procedure :: tearDown procedure :: create_av procedure :: create_calculator + procedure :: calculateAndVerifyGradient_1point_ECmid end type TestVertGradCalc2ndOrder contains @@ -117,6 +118,40 @@ contains end function create_calculator + subroutine calculateAndVerifyGradient_1point_ECmid(this, & + elevclass_bounds, topo, data, expected_vertical_gradient, & + msg) + ! Parameterized test: Setup a vertical gradient calculator for a single point with 3 + ! ECs, calculate the vertical gradient for the middle EC, and verify that the + ! vertical gradient matches the expected vertical gradient + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), intent(in) :: elevclass_bounds(:) ! elevation class bounds (should be size 4) + real(r8), intent(in) :: topo(:) ! topographic height for each EC (should be size 3) + real(r8), intent(in) :: data(:) ! data for each EC (should be size 3) + real(r8), intent(in) :: expected_vertical_gradient + character(len=*), intent(in) :: msg ! message to print if test fails + + type(vertical_gradient_calculator_2nd_order_type) :: calculator + real(r8) :: vertical_gradient(1) + + ! Check arguments + @assertEqual(4, size(elevclass_bounds)) + @assertEqual(3, size(topo)) + @assertEqual(3, size(data)) + + ! Setup + calculator = this%create_calculator( & + topo = reshape(topo, [1, 3]), & + data = reshape(data, [1, 3]), & + elevclass_bounds = elevclass_bounds) + + ! Exercise + call calculator%calc_vertical_gradient(2, vertical_gradient) + + ! Verify + @assertEqual(expected_vertical_gradient, vertical_gradient(1), tolerance=tol, message = msg) + end subroutine calculateAndVerifyGradient_1point_ECmid + @Test subroutine test_create_av(this) ! Tests the create_av helper routine @@ -144,21 +179,18 @@ contains ! Test calc_vertical_gradient with an elevation class in the middle of the range ! (standard case, not an edge case). This uses a single grid cell. class(TestVertGradCalc2ndOrder), intent(inout) :: this - type(vertical_gradient_calculator_2nd_order_type) :: calculator real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] - real(r8), parameter :: topo(1,3) = reshape([50._r8, 125._r8, 275._r8], [1,3]) - real(r8), parameter :: data(1,3) = reshape([11._r8, 12._r8, 13._r8], [1,3]) - real(r8) :: vertical_gradient(1) - real(r8) :: expected_vertical_gradient(1) - - calculator = this%create_calculator(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%calc_vertical_gradient(2, vertical_gradient) - - expected_vertical_gradient(1) = (data(1,3) - data(1,1)) / (topo(1,3) - topo(1,1)) - @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) - + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [11._r8, 12._r8, 13._r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = (data(3) - data(1)) / (topo(3) - topo(1)) + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'test_calc_vertical_gradient_ECmid') end subroutine test_calc_vertical_gradient_ECmid @Test From 685515ecff244dd0be69336fe26f8fdd68f8b65d Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 21 Apr 2016 12:05:41 -0600 Subject: [PATCH 08/61] Add a test where gradient is almost limited Test suite: None Test baseline: N/A Test namelist changes: N/A Test status: N/A Fixes: None User interface changes?: No Code review: None --- ..._vertical_gradient_calculator_2nd_order.pf | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index d531f0f0546..f74d7f5be64 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -193,6 +193,25 @@ contains msg = 'test_calc_vertical_gradient_ECmid') end subroutine test_calc_vertical_gradient_ECmid + @Test + subroutine test_calc_vertical_gradient_ECmid_almostLimitedPositiveLB(this) + ! Make sure that a positive gradient that should *almost* (but not quite) be limited + ! by the limiter isn't limited. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [11._r8, 12._r8, 19.9999_r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = (data(3) - data(1)) / (topo(3) - topo(1)) + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'test_calc_vertical_gradient_ECmid_almostLimitedPositiveLB') + end subroutine test_calc_vertical_gradient_ECmid_almostLimitedPositiveLB + @Test subroutine test_calc_vertical_gradient_ECbottom(this) ! Test calc_vertical_gradient with an elevation class at the bottom edge. This uses a From 4f82e9577b97736bd9833d442afc3b3931627848 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 21 Apr 2016 12:08:15 -0600 Subject: [PATCH 09/61] Rename tests Remove common prefix to give shorter test names Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ..._vertical_gradient_calculator_2nd_order.pf | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index f74d7f5be64..8386c059f22 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -175,7 +175,7 @@ contains end subroutine test_create_av @Test - subroutine test_calc_vertical_gradient_ECmid(this) + subroutine ECmid(this) ! Test calc_vertical_gradient with an elevation class in the middle of the range ! (standard case, not an edge case). This uses a single grid cell. class(TestVertGradCalc2ndOrder), intent(inout) :: this @@ -190,11 +190,11 @@ contains topo = topo, & data = data, & expected_vertical_gradient = expected_vertical_gradient, & - msg = 'test_calc_vertical_gradient_ECmid') - end subroutine test_calc_vertical_gradient_ECmid + msg = 'ECmid') + end subroutine ECmid @Test - subroutine test_calc_vertical_gradient_ECmid_almostLimitedPositiveLB(this) + subroutine ECmid_almostLimitedPositiveLB(this) ! Make sure that a positive gradient that should *almost* (but not quite) be limited ! by the limiter isn't limited. class(TestVertGradCalc2ndOrder), intent(inout) :: this @@ -209,11 +209,11 @@ contains topo = topo, & data = data, & expected_vertical_gradient = expected_vertical_gradient, & - msg = 'test_calc_vertical_gradient_ECmid_almostLimitedPositiveLB') - end subroutine test_calc_vertical_gradient_ECmid_almostLimitedPositiveLB + msg = 'ECmid_almostLimitedPositiveLB') + end subroutine ECmid_almostLimitedPositiveLB @Test - subroutine test_calc_vertical_gradient_ECbottom(this) + subroutine ECbottom(this) ! Test calc_vertical_gradient with an elevation class at the bottom edge. This uses a ! single grid cell. class(TestVertGradCalc2ndOrder), intent(inout) :: this @@ -232,10 +232,10 @@ contains expected_vertical_gradient(1) = (data(1,2) - data(1,1)) / (topo(1,2) - topo(1,1)) @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) - end subroutine test_calc_vertical_gradient_ECbottom + end subroutine ECbottom @Test - subroutine test_calc_vertical_gradient_ECtop(this) + subroutine ECtop(this) ! Test calc_vertical_gradient with an elevation class at the top edge. This uses a ! single grid cell. class(TestVertGradCalc2ndOrder), intent(inout) :: this @@ -254,10 +254,10 @@ contains expected_vertical_gradient(1) = (data(1,3) - data(1,2)) / (topo(1,3) - topo(1,2)) @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) - end subroutine test_calc_vertical_gradient_ECtop + end subroutine ECtop @Test - subroutine test_calc_vertical_gradient_1EC(this) + subroutine OneEC(this) ! Test calc_vertical_gradient with a single elevation class. This uses a single grid ! cell. class(TestVertGradCalc2ndOrder), intent(inout) :: this @@ -276,10 +276,10 @@ contains expected_vertical_gradient(1) = 0._r8 @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) - end subroutine test_calc_vertical_gradient_1EC + end subroutine OneEC @Test - subroutine test_calc_vertical_gradient_toposEqual(this) + subroutine toposEqual(this) ! Test calc_vertical_gradient with topo values equal - make sure this edge case is ! handled correctly. class(TestVertGradCalc2ndOrder), intent(inout) :: this @@ -298,10 +298,10 @@ contains expected_vertical_gradient(1) = 0._r8 @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) - end subroutine test_calc_vertical_gradient_toposEqual + end subroutine toposEqual @Test - subroutine test_calc_vertical_gradient_multiplePoints(this) + subroutine multiplePoints(this) ! Test calc_vertical_gradient with multiple grid cells. One has topo values equal, ! two are normal cases. class(TestVertGradCalc2ndOrder), intent(inout) :: this @@ -334,7 +334,7 @@ contains expected_vertical_gradient(3) = (data(3,2) - data(3,1)) / (topo(3,2) - topo(3,1)) @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) - end subroutine test_calc_vertical_gradient_multiplePoints + end subroutine multiplePoints end module test_vertical_gradient_calculator_2nd_order From fda0e5a060fd748e60e108798703656212a70a8f Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 21 Apr 2016 12:31:58 -0600 Subject: [PATCH 10/61] Add more tests that should almost (but not quite) trigger the limiter Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ..._vertical_gradient_calculator_2nd_order.pf | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index 8386c059f22..1b5778f648d 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -196,7 +196,7 @@ contains @Test subroutine ECmid_almostLimitedPositiveLB(this) ! Make sure that a positive gradient that should *almost* (but not quite) be limited - ! by the limiter isn't limited. + ! by the limiter (due to the lower bound) isn't limited. class(TestVertGradCalc2ndOrder), intent(inout) :: this real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] @@ -212,6 +212,63 @@ contains msg = 'ECmid_almostLimitedPositiveLB') end subroutine ECmid_almostLimitedPositiveLB + @Test + subroutine ECmid_almostLimitedPositiveUB(this) + ! Make sure that a positive gradient that should *almost* (but not quite) be limited + ! by the limiter (due to the upper bound) isn't limited. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [10.0001_r8, 12._r8, 13._r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = (data(3) - data(1)) / (topo(3) - topo(1)) + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_almostLimitedPositiveUB') + end subroutine ECmid_almostLimitedPositiveUB + + @Test + subroutine ECmid_almostLimitedNegativeLB(this) + ! Make sure that a negative gradient that should *almost* (but not quite) be limited + ! by the limiter (due to the lower bound) isn't limited. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [13._r8, 12._r8, 4.0001_r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = (data(3) - data(1)) / (topo(3) - topo(1)) + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_almostLimitedNegativeLB') + end subroutine ECmid_almostLimitedNegativeLB + + @Test + subroutine ECmid_almostLimitedNegativeUB(this) + ! Make sure that a negative gradient that should *almost* (but not quite) be limited + ! by the limiter (due to the upper bound) isn't limited. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [13.9999_r8, 12._r8, 11._r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = (data(3) - data(1)) / (topo(3) - topo(1)) + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_almostLimitedNegativeUB') + end subroutine ECmid_almostLimitedNegativeUB + @Test subroutine ECbottom(this) ! Test calc_vertical_gradient with an elevation class at the bottom edge. This uses a From 25fe31dcf3bfa8c546550a801de3aef9a6aab68a Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 21 Apr 2016 14:01:59 -0600 Subject: [PATCH 11/61] Add code to limit the gradient, and associated unit tests The limiter code is from Bill Lipscomb Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...vertical_gradient_calculator_2nd_order.F90 | 90 ++++++++++++- ..._vertical_gradient_calculator_2nd_order.pf | 120 +++++++++++++++++- 2 files changed, 208 insertions(+), 2 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index 6f1c7588a06..6a0593b8667 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -40,6 +40,7 @@ module vertical_gradient_calculator_2nd_order procedure, private :: set_data_from_attr_vect ! extract data from an attribute vector procedure, private :: check_topo ! check topographic heights + procedure, private :: limit_gradient end type vertical_gradient_calculator_2nd_order_type @@ -134,7 +135,8 @@ subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) integer :: i integer :: ec_low ! elevation class index to use as the lower bound of the gradient integer :: ec_high ! elevation class index to use as the upper bound of the gradient - + logical :: two_sided ! true if we're estimating the gradient with a two-sided difference + character(len=*), parameter :: subname = 'calc_vertical_gradient' !----------------------------------------------------------------------- @@ -151,17 +153,23 @@ subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) ! Do the calculations + ! Start by assuming we're doing a two-sided difference; we'll set this to false if we aren't + two_sided = .true. + if (this%min_elevation_class == this%max_elevation_class) then vertical_gradient(:) = 0._r8 + two_sided = .false. else if (elevation_class == this%min_elevation_class) then ec_low = elevation_class ec_high = elevation_class + 1 + two_sided = .false. else if (elevation_class == this%max_elevation_class) then ec_low = elevation_class - 1 ec_high = elevation_class + two_sided = .false. else ec_low = elevation_class - 1 ec_high = elevation_class + 1 @@ -177,6 +185,10 @@ subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) end if end do + if (two_sided) then + call this%limit_gradient(elevation_class, ec_low, ec_high, vertical_gradient) + end if + end if end subroutine calc_vertical_gradient @@ -285,6 +297,82 @@ subroutine check_topo(this) end subroutine check_topo + !----------------------------------------------------------------------- + subroutine limit_gradient(this, k, ec_low, ec_high, vertical_gradient) + ! + ! !DESCRIPTION: + ! Limit the gradient: Ensure that the interface values lie inside the range defined + ! by the max and min of the mean values in this class and its 2 adjacent neighbors. + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_2nd_order_type), intent(in) :: this + integer , intent(in) :: k ! elevation class index + integer , intent(in) :: ec_low ! elevation class index used as the lower bound of the gradient + integer , intent(in) :: ec_high ! elevation class index used as the upper bound of the gradient + real(r8), intent(inout) :: vertical_gradient(:) + ! + ! !LOCAL VARIABLES: + integer :: i + real(r8) :: deviation_high + real(r8) :: deviation_low + real(r8) :: deviation_max + real(r8) :: deviation_min + real(r8) :: diff_max + real(r8) :: diff_min + real(r8) :: factor1 + real(r8) :: factor2 + real(r8) :: limiting_factor + + character(len=*), parameter :: subname = 'limit_gradient' + !----------------------------------------------------------------------- + + ! Basic idea: In 1D with a linear reconstruction, the extreme values of the data will + ! lie at the interfaces between adjacent elevation classes. The interface values + ! should not lie outside the range defined by the max and min of the mean values in + ! this class and its 2 adjacent neighbors. + + ! This code only works correctly if we're doing a two-sided difference (otherwise, + ! one of diff_min or diff_max will be 0, leading to 0 gradient - when in fact we + ! don't want to do any limiting for a one-sided difference). + SHR_ASSERT(ec_low < k, subname//': Only works for two-sided difference: must have ec_low < k') + SHR_ASSERT(ec_high > k, subname//': Only works for two-sided difference: must have ec_high > k') + + do i = 1, this%num_points + ! First compute the max and min values of the deviation of the data from its mean + ! value. With a linear gradient, the max differences must lie at the adjacent + ! interfaces. + deviation_high = vertical_gradient(i) * (this%elevclass_bounds(k) - this%topo(i,k)) + deviation_low = vertical_gradient(i) * (this%elevclass_bounds(k-1) - this%topo(i,k)) + deviation_max = max(deviation_high, deviation_low) + deviation_min = min(deviation_high, deviation_low) + + ! Now compute the max and min of the data in the cell and its nearest neighbors. + ! (Actually, the difference between this max/min value and the mean value in the + ! current class.) + diff_max = max(this%field(i,ec_high), this%field(i,k), this%field(i,ec_low)) - this%field(i,k) + diff_min = min(this%field(i,ec_high), this%field(i,k), this%field(i,ec_low)) - this%field(i,k) + + ! Now limit the gradient using the information computed above. + + if (abs(deviation_min) > 0._r8) then + factor1 = max(0._r8, diff_min/deviation_min) + else + factor1 = 1._r8 + endif + + if (abs(deviation_max) > 0._r8) then + factor2 = max(0._r8, diff_max/deviation_max) + else + factor2 = 1._r8 + endif + + ! limiting factor will lie between 0 and 1 + limiting_factor = min(1._r8, factor1, factor2) + vertical_gradient(i) = vertical_gradient(i) * limiting_factor + end do + + end subroutine limit_gradient + end module vertical_gradient_calculator_2nd_order diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index 1b5778f648d..aa01b8ba0e6 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -283,7 +283,7 @@ contains calculator = this%create_calculator(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - + call calculator%calc_vertical_gradient(1, vertical_gradient) expected_vertical_gradient(1) = (data(1,2) - data(1,1)) / (topo(1,2) - topo(1,1)) @@ -357,6 +357,124 @@ contains end subroutine toposEqual + ! ------------------------------------------------------------------------ + ! Tests that trigger the limiter + ! ------------------------------------------------------------------------ + + @Test + subroutine ECmid_limitedLocalMaximum(this) + ! If values go low, high, low, then gradient should be 0 + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [11._r8, 12._r8, 10._r8] + real(r8), parameter :: expected_vertical_gradient = 0._r8 + + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_limitedLocalMaximum') + end subroutine ECmid_limitedLocalMaximum + + @Test + subroutine ECmid_limitedLocalMinimum(this) + ! If values go high, low, high, then gradient should be 0 + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [13._r8, 12._r8, 14._r8] + real(r8), parameter :: expected_vertical_gradient = 0._r8 + + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_limitedLocalMinimum') + end subroutine ECmid_limitedLocalMinimum + + @Test + subroutine ECmid_limitedPositiveLB(this) + ! Make sure that a positive gradient that should be limited by the limiter (due to the + ! lower bound) is in fact limited. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [11._r8, 12._r8, 21._r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = 1._r8/25._r8 + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_limitedPositiveLB') + end subroutine ECmid_limitedPositiveLB + + @Test + subroutine ECmid_limitedPositiveUB(this) + ! Make sure that a positive gradient that should be limited by the limiter (due to the + ! upper bound) is in fact limited. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [9._r8, 12._r8, 13._r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = 1._r8/75._r8 + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_limitedPositiveUB') + end subroutine ECmid_limitedPositiveUB + + @Test + subroutine ECmid_limitedNegativeLB(this) + ! Make sure that a negative gradient that should be limited by the limiter (due to the + ! lower bound) is in fact limited. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [13._r8, 12._r8, 3._r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = -1._r8/25._r8 + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_limitedNegativeLB') + end subroutine ECmid_limitedNegativeLB + + @Test + subroutine ECmid_limitedNegativeUB(this) + ! Make sure that a negative gradient that should be limited by the limiter (due to the + ! upper bound) is in fact limited. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [15._r8, 12._r8, 11._r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = -1._r8/75._r8 + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_limitedNegativeUB') + end subroutine ECmid_limitedNegativeUB + + ! ------------------------------------------------------------------------ + ! Tests with multiple points + ! ------------------------------------------------------------------------ + @Test subroutine multiplePoints(this) ! Test calc_vertical_gradient with multiple grid cells. One has topo values equal, From 9950ba004bc066f5485e27808b6dd263582897f4 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 21 Apr 2016 14:05:13 -0600 Subject: [PATCH 12/61] Tweak ECbottom unit test Make bottom topo value off-center: it seems this is a slightly stronger test Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- .../test_vertical_gradient_calculator_2nd_order.pf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index aa01b8ba0e6..c5b30d23b24 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -276,7 +276,7 @@ contains class(TestVertGradCalc2ndOrder), intent(inout) :: this type(vertical_gradient_calculator_2nd_order_type) :: calculator real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] - real(r8), parameter :: topo(1,3) = reshape([50._r8, 125._r8, 275._r8], [1,3]) + real(r8), parameter :: topo(1,3) = reshape([40._r8, 125._r8, 275._r8], [1,3]) real(r8), parameter :: data(1,3) = reshape([11._r8, 12._r8, 13._r8], [1,3]) real(r8) :: vertical_gradient(1) real(r8) :: expected_vertical_gradient(1) From 5867dbc0c6f6aa81640ce40cce5d6a7790e77a45 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 21 Apr 2016 14:31:09 -0600 Subject: [PATCH 13/61] Add multi-point test, and a comment in production code Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...vertical_gradient_calculator_2nd_order.F90 | 2 + ..._vertical_gradient_calculator_2nd_order.pf | 38 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index 6a0593b8667..d8edb388c5f 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -304,6 +304,8 @@ subroutine limit_gradient(this, k, ec_low, ec_high, vertical_gradient) ! Limit the gradient: Ensure that the interface values lie inside the range defined ! by the max and min of the mean values in this class and its 2 adjacent neighbors. ! + ! Should only be called for two-sided differences (ec_low < k < ec_high) + ! ! !ARGUMENTS: class(vertical_gradient_calculator_2nd_order_type), intent(in) :: this integer , intent(in) :: k ! elevation class index diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index c5b30d23b24..e0fbdaafda1 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -510,6 +510,42 @@ contains @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) end subroutine multiplePoints - + + @Test + subroutine multiplePoints_someLimited(this) + ! Test with multiple grid cells, some (but not all) of which trigger the limiter. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + type(vertical_gradient_calculator_2nd_order_type) :: calculator + + integer, parameter :: npts = 3 + integer, parameter :: nelev = 3 + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + ! In the following, each line is one elevation class (with all points for that + ! elevation class) + real(r8), parameter :: topo(npts,nelev) = reshape( & + [50._r8, 50._r8, 50._r8, & + 125._r8, 125._r8, 125._r8, & + 275._r8, 275._r8, 275._r8], & + [npts,nelev]) + ! points are: limited by lower bound, non-limited, limited by upper bound + real(r8), parameter :: data(npts,nelev) = reshape( & + [11._r8, 11._r8, 9._r8, & + 12._r8, 12._r8, 12._r8, & + 21._r8, 13._r8, 13._r8], & + [npts,nelev]) + + real(r8) :: vertical_gradient(npts) + real(r8) :: expected_vertical_gradient(npts) + + calculator = this%create_calculator(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + call calculator%calc_vertical_gradient(2, vertical_gradient) + + expected_vertical_gradient(1) = 1._r8/25._r8 + expected_vertical_gradient(2) = 2._r8/225._r8 + expected_vertical_gradient(3) = 1._r8/75._r8 + @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) + end subroutine multiplePoints_someLimited end module test_vertical_gradient_calculator_2nd_order From f93e16a405963ffb1092d7a73ee34cc8d1af5848 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 21 Apr 2016 14:46:40 -0600 Subject: [PATCH 14/61] Demonstrate that it works to have topo going high to low Note that this required deleting the check_topo call I'm going to revert this commit: it's for demonstration purposes only Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...vertical_gradient_calculator_2nd_order.F90 | 2 - ..._vertical_gradient_calculator_2nd_order.pf | 100 ++++++++++++++++++ 2 files changed, 100 insertions(+), 2 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index d8edb388c5f..5c204a90324 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -103,8 +103,6 @@ function constructor(attr_vect, fieldname, toponame, & this%elevclass_bounds(:) = elevclass_bounds(:) call this%set_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names) - call this%check_topo() - end function constructor diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index e0fbdaafda1..402197f3daa 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -471,6 +471,106 @@ contains msg = 'ECmid_limitedNegativeUB') end subroutine ECmid_limitedNegativeUB + ! ------------------------------------------------------------------------ + ! Tests of topography going from high to low rather than low to high. These don't + ! really need to work, but if they don't, then we should put some assertions in the + ! production code to ensure that nobody tries to create a vertical gradient calculator + ! with topography going from high to low. + ! ------------------------------------------------------------------------ + + @Test + subroutine ECmid_topoHighToLow(this) + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [300._r8, 200._r8, 100._r8, 0._r8] + real(r8), parameter :: topo(3) = [275._r8, 125._r8, 50._r8] + real(r8), parameter :: data(3) = [13._r8, 12._r8, 11._r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = (data(3) - data(1)) / (topo(3) - topo(1)) + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_topoHighToLow') + end subroutine ECmid_topoHighToLow + + @Test + subroutine ECmid_limitedPositiveLB_topoHighToLow(this) + ! Make sure that a positive gradient that should be limited by the limiter (due to the + ! lower bound) is in fact limited. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [300._r8, 200._r8, 100._r8, 0._r8] + real(r8), parameter :: topo(3) = [275._r8, 125._r8, 50._r8] + real(r8), parameter :: data(3) = [21._r8, 12._r8, 11._r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = 1._r8/25._r8 + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_limitedPositiveLB_topoHighToLow') + end subroutine ECmid_limitedPositiveLB_topoHighToLow + + @Test + subroutine ECmid_limitedPositiveUB_topoHighToLow(this) + ! Make sure that a positive gradient that should be limited by the limiter (due to the + ! upper bound) is in fact limited. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [300._r8, 200._r8, 100._r8, 0._r8] + real(r8), parameter :: topo(3) = [275._r8, 125._r8, 50._r8] + real(r8), parameter :: data(3) = [13._r8, 12._r8, 9._r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = 1._r8/75._r8 + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_limitedPositiveUB_topoHighToLow') + end subroutine ECmid_limitedPositiveUB_topoHighToLow + + @Test + subroutine ECmid_limitedNegativeLB_topoHighToLow(this) + ! Make sure that a negative gradient that should be limited by the limiter (due to the + ! lower bound) is in fact limited. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [300._r8, 200._r8, 100._r8, 0._r8] + real(r8), parameter :: topo(3) = [275._r8, 125._r8, 50._r8] + real(r8), parameter :: data(3) = [3._r8, 12._r8, 13._r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = -1._r8/25._r8 + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_limitedNegativeLB_topoHighToLow') + end subroutine ECmid_limitedNegativeLB_topoHighToLow + + @Test + subroutine ECmid_limitedNegativeUB_topoHighToLow(this) + ! Make sure that a negative gradient that should be limited by the limiter (due to the + ! upper bound) is in fact limited. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + real(r8), parameter :: elevclass_bounds(4) = [300._r8, 200._r8, 100._r8, 0._r8] + real(r8), parameter :: topo(3) = [275._r8, 125._r8, 50._r8] + real(r8), parameter :: data(3) = [11._r8, 12._r8, 15._r8] + real(r8) :: expected_vertical_gradient + + expected_vertical_gradient = -1._r8/75._r8 + call this%calculateAndVerifyGradient_1point_ECmid( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + expected_vertical_gradient = expected_vertical_gradient, & + msg = 'ECmid_limitedNegativeUB_topoHighToLow') + end subroutine ECmid_limitedNegativeUB_topoHighToLow + ! ------------------------------------------------------------------------ ! Tests with multiple points ! ------------------------------------------------------------------------ From 0cd0cde9f8325fff4bcd020ae21a8597fb466a6c Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 21 Apr 2016 14:47:57 -0600 Subject: [PATCH 15/61] Revert "Demonstrate that it works to have topo going high to low" This reverts commit 4a0888f7e1be3d72d8b4b04daa19a6ac75d6e433. The desire to check topo values makes it tricky to support topos going high to low, and I don't see much value in supporting this for now. --- ...vertical_gradient_calculator_2nd_order.F90 | 2 + ..._vertical_gradient_calculator_2nd_order.pf | 100 ------------------ 2 files changed, 2 insertions(+), 100 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index 5c204a90324..d8edb388c5f 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -103,6 +103,8 @@ function constructor(attr_vect, fieldname, toponame, & this%elevclass_bounds(:) = elevclass_bounds(:) call this%set_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names) + call this%check_topo() + end function constructor diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index 402197f3daa..e0fbdaafda1 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -471,106 +471,6 @@ contains msg = 'ECmid_limitedNegativeUB') end subroutine ECmid_limitedNegativeUB - ! ------------------------------------------------------------------------ - ! Tests of topography going from high to low rather than low to high. These don't - ! really need to work, but if they don't, then we should put some assertions in the - ! production code to ensure that nobody tries to create a vertical gradient calculator - ! with topography going from high to low. - ! ------------------------------------------------------------------------ - - @Test - subroutine ECmid_topoHighToLow(this) - class(TestVertGradCalc2ndOrder), intent(inout) :: this - real(r8), parameter :: elevclass_bounds(4) = [300._r8, 200._r8, 100._r8, 0._r8] - real(r8), parameter :: topo(3) = [275._r8, 125._r8, 50._r8] - real(r8), parameter :: data(3) = [13._r8, 12._r8, 11._r8] - real(r8) :: expected_vertical_gradient - - expected_vertical_gradient = (data(3) - data(1)) / (topo(3) - topo(1)) - call this%calculateAndVerifyGradient_1point_ECmid( & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - expected_vertical_gradient = expected_vertical_gradient, & - msg = 'ECmid_topoHighToLow') - end subroutine ECmid_topoHighToLow - - @Test - subroutine ECmid_limitedPositiveLB_topoHighToLow(this) - ! Make sure that a positive gradient that should be limited by the limiter (due to the - ! lower bound) is in fact limited. - class(TestVertGradCalc2ndOrder), intent(inout) :: this - real(r8), parameter :: elevclass_bounds(4) = [300._r8, 200._r8, 100._r8, 0._r8] - real(r8), parameter :: topo(3) = [275._r8, 125._r8, 50._r8] - real(r8), parameter :: data(3) = [21._r8, 12._r8, 11._r8] - real(r8) :: expected_vertical_gradient - - expected_vertical_gradient = 1._r8/25._r8 - call this%calculateAndVerifyGradient_1point_ECmid( & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - expected_vertical_gradient = expected_vertical_gradient, & - msg = 'ECmid_limitedPositiveLB_topoHighToLow') - end subroutine ECmid_limitedPositiveLB_topoHighToLow - - @Test - subroutine ECmid_limitedPositiveUB_topoHighToLow(this) - ! Make sure that a positive gradient that should be limited by the limiter (due to the - ! upper bound) is in fact limited. - class(TestVertGradCalc2ndOrder), intent(inout) :: this - real(r8), parameter :: elevclass_bounds(4) = [300._r8, 200._r8, 100._r8, 0._r8] - real(r8), parameter :: topo(3) = [275._r8, 125._r8, 50._r8] - real(r8), parameter :: data(3) = [13._r8, 12._r8, 9._r8] - real(r8) :: expected_vertical_gradient - - expected_vertical_gradient = 1._r8/75._r8 - call this%calculateAndVerifyGradient_1point_ECmid( & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - expected_vertical_gradient = expected_vertical_gradient, & - msg = 'ECmid_limitedPositiveUB_topoHighToLow') - end subroutine ECmid_limitedPositiveUB_topoHighToLow - - @Test - subroutine ECmid_limitedNegativeLB_topoHighToLow(this) - ! Make sure that a negative gradient that should be limited by the limiter (due to the - ! lower bound) is in fact limited. - class(TestVertGradCalc2ndOrder), intent(inout) :: this - real(r8), parameter :: elevclass_bounds(4) = [300._r8, 200._r8, 100._r8, 0._r8] - real(r8), parameter :: topo(3) = [275._r8, 125._r8, 50._r8] - real(r8), parameter :: data(3) = [3._r8, 12._r8, 13._r8] - real(r8) :: expected_vertical_gradient - - expected_vertical_gradient = -1._r8/25._r8 - call this%calculateAndVerifyGradient_1point_ECmid( & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - expected_vertical_gradient = expected_vertical_gradient, & - msg = 'ECmid_limitedNegativeLB_topoHighToLow') - end subroutine ECmid_limitedNegativeLB_topoHighToLow - - @Test - subroutine ECmid_limitedNegativeUB_topoHighToLow(this) - ! Make sure that a negative gradient that should be limited by the limiter (due to the - ! upper bound) is in fact limited. - class(TestVertGradCalc2ndOrder), intent(inout) :: this - real(r8), parameter :: elevclass_bounds(4) = [300._r8, 200._r8, 100._r8, 0._r8] - real(r8), parameter :: topo(3) = [275._r8, 125._r8, 50._r8] - real(r8), parameter :: data(3) = [11._r8, 12._r8, 15._r8] - real(r8) :: expected_vertical_gradient - - expected_vertical_gradient = -1._r8/75._r8 - call this%calculateAndVerifyGradient_1point_ECmid( & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - expected_vertical_gradient = expected_vertical_gradient, & - msg = 'ECmid_limitedNegativeUB_topoHighToLow') - end subroutine ECmid_limitedNegativeUB_topoHighToLow - ! ------------------------------------------------------------------------ ! Tests with multiple points ! ------------------------------------------------------------------------ From e9e7e70e9abcc2f8de5bac8dd54dd21eeea46303 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 21 Apr 2016 16:03:26 -0600 Subject: [PATCH 16/61] Add a test demonstrating non-monotonicity This test could be removed at some point: it's just here to demonstrate current behavior Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ..._vertical_gradient_calculator_2nd_order.pf | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index e0fbdaafda1..fa2fb683116 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -471,6 +471,57 @@ contains msg = 'ECmid_limitedNegativeUB') end subroutine ECmid_limitedNegativeUB + ! ------------------------------------------------------------------------ + ! Test that demonstrates that we can still have non-monotonic behavior + ! + ! Unlike most tests, this test isn't necessarily something we want - it is just a + ! demonstration of current behavior. So this test can be removed if this behavior + ! changes. + ! ------------------------------------------------------------------------ + + @Test + subroutine evenWithLimiter_canStillBeNonMonotonic(this) + ! This test demonstrates that, even though the incoming values are monotonic, the + ! interpolated values are not. + ! + ! Unlike most tests, this test isn't necessarily something we want - it is just a + ! demonstration of current behavior. So this test can be removed if this behavior + ! changes. + class(TestVertGradCalc2ndOrder), intent(inout) :: this + type(vertical_gradient_calculator_2nd_order_type) :: calculator + real(r8), parameter :: elevclass_bounds(5) = [0._r8, 100._r8, 200._r8, 300._r8, 400._r8] + real(r8), parameter :: topo(1,4) = reshape([50._r8, 125._r8, 275._r8, 350._r8], [1,4]) + real(r8), parameter :: data(1,4) = reshape([9._r8, 12._r8, 13._r8 , 14._r8], [1,4]) + real(r8) :: vertical_gradient_ec2(1) + real(r8) :: vertical_gradient_ec3(1) + real(r8) :: value_200m_ec2 + real(r8) :: value_200m_ec3 + real(r8) :: value_199m + real(r8) :: value_201m + + calculator = this%create_calculator(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + call calculator%calc_vertical_gradient(2, vertical_gradient_ec2) + call calculator%calc_vertical_gradient(3, vertical_gradient_ec3) + + ! Show non-monotonicity in two ways: + + ! (1) value at 200m in EC2 > value at 200m in EC3 + value_200m_ec2 = data(1,2) + vertical_gradient_ec2(1) * (200._r8 - topo(1,2)) + value_200m_ec3 = data(1,3) + vertical_gradient_ec3(1) * (200._r8 - topo(1,3)) + @assertEqual(13._r8, value_200m_ec2, tolerance=tol) + ! In the following, use 12.9 rather than 13 to show that value_200m_ec3 is even less + ! than 12.9 (i.e., it's not just a roundoff problem) + @assertGreaterThan(12.9_r8, value_200m_ec3) + + ! (2) value at 199m (in EC2) > value at 201m (in EC3) + value_199m = data(1,2) + vertical_gradient_ec2(1) * (199._r8 - topo(1,2)) + value_201m = data(1,3) + vertical_gradient_ec3(1) * (201._r8 - topo(1,3)) + @assertGreaterThan(value_199m, value_201m) + + end subroutine evenWithLimiter_canStillBeNonMonotonic + ! ------------------------------------------------------------------------ ! Tests with multiple points ! ------------------------------------------------------------------------ From d30f22ce40151f4344cf01afe229b798d53a4d78 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 21 Apr 2016 19:47:27 -0600 Subject: [PATCH 17/61] Comment out call to check_topo It's important for topographic heights to be within bounds in order for the limiter to be applied correctly. However, this currently isn't the case for some of the old TG forcing data. At a glance, it looks like the problems are just outside of Greenland, so this should be okay. When we have new TG forcing data, we should try uncommenting this call to check_topo. Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- .../driver/vertical_gradient_calculator_2nd_order.F90 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index d8edb388c5f..bb59a75d540 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -67,6 +67,7 @@ function constructor(attr_vect, fieldname, toponame, & ! (topographic heights can lie above the arbitrary upper bound of the elevation ! class). (This pre-condition is mainly important for the sake of calculating the ! limiter.) + ! TODO(wjs, 2016-04-21) Currently this pre-condition is not checked: see below. ! ! The attribute vector is assumed to have fields named fieldname // ! elevclass_names(1), toponame // elevclass_names(1), etc. @@ -103,7 +104,14 @@ function constructor(attr_vect, fieldname, toponame, & this%elevclass_bounds(:) = elevclass_bounds(:) call this%set_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names) - call this%check_topo() + ! TODO(wjs, 2016-04-21) Uncomment this call to check_topo. It's important for + ! topographic heights to be within bounds in order for the limiter to be applied + ! correctly. However, this currently isn't the case for some of the old TG forcing + ! data. At a glance, it looks like the problems are just outside of Greenland, so this + ! should be okay. When we have new TG forcing data, we should try uncommenting this + ! call to check_topo. + + ! call this%check_topo() end function constructor From 0bec14e441754e42da5baea46a0a017c7ad0d596 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 22 Apr 2016 09:33:21 -0600 Subject: [PATCH 18/61] Ensure that elevation class bounds are monotonically increasing Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...vertical_gradient_calculator_2nd_order.F90 | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index bb59a75d540..17c9fae5a42 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -32,12 +32,14 @@ module vertical_gradient_calculator_2nd_order ! Bounds of each elevation class. This array has one more element than the number of ! elevation classes, since it contains lower and upper bounds for each elevation - ! class. The indices go (min_elevation_class-1):max_elevation_class + ! class. The indices go (min_elevation_class-1):max_elevation_class. These bounds + ! are guaranteed to be monotonically increasing. real(r8), allocatable :: elevclass_bounds(:) contains procedure :: calc_vertical_gradient - + + procedure, private :: check_elevclass_bounds ! check for monotonicity of elevclass_bounds procedure, private :: set_data_from_attr_vect ! extract data from an attribute vector procedure, private :: check_topo ! check topographic heights procedure, private :: limit_gradient @@ -60,6 +62,8 @@ function constructor(attr_vect, fieldname, toponame, & ! Creates a vertical_gradient_calculator_2nd_order_type object by reading the ! necessary data from the provided attribute vector. ! + ! Pre-condition: elevclass_bounds must be monotonically increasing. + ! ! Pre-condition: Topographic heights in the attribute vector must all lie inside the ! bounds of their respective elevation class (given by elevclass_bounds), with the ! possible exception of the lowest elevation class (topographic heights can lie below @@ -102,6 +106,8 @@ function constructor(attr_vect, fieldname, toponame, & this%max_elevation_class = max_elevation_class allocate(this%elevclass_bounds((min_elevation_class-1):max_elevation_class)) this%elevclass_bounds(:) = elevclass_bounds(:) + call this%check_elevclass_bounds() + call this%set_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names) ! TODO(wjs, 2016-04-21) Uncomment this call to check_topo. It's important for @@ -201,6 +207,34 @@ subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) end subroutine calc_vertical_gradient + !----------------------------------------------------------------------- + subroutine check_elevclass_bounds(this) + ! + ! !DESCRIPTION: + ! Ensure that elevclass_bounds are monotonically increasing; abort if there is a + ! problem + ! + ! (In principle, we could also handle monotonically decreasing elevclass_bounds, but + ! that would require generalizing some code, such as in check_topo.) + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_2nd_order_type), intent(in) :: this + ! + ! !LOCAL VARIABLES: + integer :: i + + character(len=*), parameter :: subname = 'check_elevclass_bounds' + !----------------------------------------------------------------------- + + do i = this%min_elevation_class, this%max_elevation_class + if (this%elevclass_bounds(i-1) >= this%elevclass_bounds(i)) then + write(logunit,*) subname, ': ERROR: elevclass_bounds must be monotonically increasing' + write(logunit,*) 'elevclass_bounds = ', this%elevclass_bounds + call shr_sys_abort(subname//': ERROR: elevclass_bounds must be monotonically increasing') + end if + end do + + end subroutine check_elevclass_bounds !----------------------------------------------------------------------- subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame, elevclass_names) From 369d3bd5ee1b84830518ddff7f01a2b0399272c9 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Tue, 26 Apr 2016 14:53:31 -0600 Subject: [PATCH 19/61] Add a function to invert a tridiagonal matrix This was written by Bill Lipscomb Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- share/csm_share/shr/CMakeLists.txt | 18 ++- share/csm_share/shr/shr_matrix_mod.F90 | 145 ++++++++++++++++++ share/csm_share/test/unit/CMakeLists.txt | 2 + .../test/unit/shr_matrix_test/CMakeLists.txt | 20 +++ .../test_tridiagonal_inverse.pf | 88 +++++++++++ 5 files changed, 270 insertions(+), 3 deletions(-) create mode 100644 share/csm_share/shr/shr_matrix_mod.F90 create mode 100644 share/csm_share/test/unit/shr_matrix_test/CMakeLists.txt create mode 100644 share/csm_share/test/unit/shr_matrix_test/test_tridiagonal_inverse.pf diff --git a/share/csm_share/shr/CMakeLists.txt b/share/csm_share/shr/CMakeLists.txt index 87b63287a56..ba9ec40f735 100644 --- a/share/csm_share/shr/CMakeLists.txt +++ b/share/csm_share/shr/CMakeLists.txt @@ -7,9 +7,21 @@ sourcelist_to_parent(share_genf90_sources) list(APPEND share_sources "${share_genf90_sources}") -list(APPEND share_sources shr_file_mod.F90 shr_kind_mod.F90 shr_const_mod.F90 - shr_sys_mod.F90 shr_log_mod.F90 shr_orb_mod.F90 shr_spfn_mod.F90 shr_strconvert_mod.F90 - shr_nl_mod.F90 shr_precip_mod.F90 shr_string_mod.F90 shr_timer_mod.F90 shr_vmath_mod.F90 +list(APPEND share_sources + shr_file_mod.F90 + shr_kind_mod.F90 + shr_const_mod.F90 + shr_sys_mod.F90 + shr_log_mod.F90 + shr_orb_mod.F90 + shr_spfn_mod.F90 + shr_strconvert_mod.F90 + shr_matrix_mod.F90 + shr_nl_mod.F90 + shr_precip_mod.F90 + shr_string_mod.F90 + shr_timer_mod.F90 + shr_vmath_mod.F90 shr_wv_sat_mod.F90) # Build a separate list containing the mct wrapper and its dependencies. That diff --git a/share/csm_share/shr/shr_matrix_mod.F90 b/share/csm_share/shr/shr_matrix_mod.F90 new file mode 100644 index 00000000000..bdfcf35a97d --- /dev/null +++ b/share/csm_share/shr/shr_matrix_mod.F90 @@ -0,0 +1,145 @@ +module shr_matrix_mod + + ! This module contains routines for working with matrices. + + ! If possible, use routines from BLAS / LAPACK. This module should only contain + ! routines that go beyond what is available in BLAS / LAPACK. + +#include "shr_assert.h" + use shr_kind_mod, only : r8 => SHR_KIND_R8 + use shr_log_mod, only : errMsg => shr_log_errMsg + + implicit none + private + save + + public :: tridiagonal_inverse ! invert a tridiagonal matrix + +contains + + !----------------------------------------------------------------------- + subroutine tridiagonal_inverse(a, b, c, Tinv) + ! + ! !DESCRIPTION: + ! Inverts a tridiagonal matrix. + ! + ! All input / output arrays should have the same number of elements. + ! + ! Author: Bill Lipscomb + ! + ! !USES: + ! + ! !ARGUMENTS: + real(r8), intent(in) :: a(:) ! Center diagonal + real(r8), intent(in) :: b(:) ! Upper diagonal (superdiagonal); b(n) is ignored + real(r8), intent(in) :: c(:) ! Lower diagonal (subdiagonal); c(n) is ignored + real(r8), intent(out) :: Tinv(:,:) ! Inverse matrix + ! + ! !LOCAL VARIABLES: + integer :: n ! matrix dimension + integer :: i, j, ii + real(r8) :: theta(0:size(a)) + real(r8) :: phi(1:(size(a)+1)) + real(r8) :: detT ! determinant of inverse matrix + real(r8) :: b_product ! cumulative product of b coefficients + real(r8) :: c_product ! cumulative product of c coefficients + + character(len=*), parameter :: subname = 'tridiagonal_inverse' + !----------------------------------------------------------------------- + + !------------------------------------------------------------------------ + ! Here is the formula for coefficients of the inverse of a tridiagonal matrix: + ! + ! | a_1 b_1 | + ! | | + ! | c_1 a_2 b_2 | + ! | | + ! T = | c_2 a_3 ... | + ! | | + ! | ... ... b_n-1 | + ! | | + ! | c_n-1 a_n | + ! + ! Tinv(i,j) = (-1)^(i+j) * (b_i ... b_{j-1}) * theta_{i-1} * phi_{j+1} / theta_n if i <= j + ! + ! Tinv(i,j) = (-1)^(i+j) * (c_j ... b_{i-1}) * theta_{j-1} * phi_{i+1} / theta_n if i > j + ! + ! where theta_0 = 1 + ! theta_1 = a_1 + ! theta_i = a_i*theta_{i-1} - b_{i-1}*c_{i-1}*theta_{i-2} for i = 2, 3, ..., n + ! + ! phi_{n+1} = 1 + ! phi_n = a_n + ! phi_i = a_i*phi_{i+1} - b_i*c_i*phi_{i+2} for i = n-1, n-2, ..., 1 + ! + ! Note: For i = j, the b products are evaluated as b_i ... b_{j-1} = 1. + !------------------------------------------------------------------------ + + n = size(a) + SHR_ASSERT((size(b) == n), errMsg(__FILE__, __LINE__)) + SHR_ASSERT((size(c) == n), errMsg(__FILE__, __LINE__)) + SHR_ASSERT_ALL((shape(Tinv) == [n,n]), errMsg(__FILE__, __LINE__)) + + ! Compute theta recursively + ! Note: theta(n) is the determinant + + theta(0) = 1._r8 + theta(1) = a(1) + + do i = 2, n + theta(i) = a(i)*theta(i-1) - b(i-1)*c(i-1)*theta(i-2) + enddo + + detT = theta(n) + + ! Compute phi recursively + + phi(n+1) = 1._r8 + phi(n) = a(n) + + do i = n-1, 1, -1 + phi(i) = a(i)*phi(i+1) - b(i)*c(i)*phi(i+2) + enddo + + ! Compute coefficients of Tinv + + do j = 1, n + do i = 1, n + + if (i <= j) then + + ! compute product of b terms from i to j-1 + + b_product = 1 ! if i = j + if (i < j) then + do ii = i, j-1 + b_product = b_product*b(ii) + enddo + endif + + ! compute coefficient + + Tinv(i,j) = (-1)**(i+j) * b_product * theta(i-1) * phi(j+1) / detT + + else ! i > j + + ! compute product of c terms from j to i-1 + + c_product = 1 + do ii = j, i-1 + c_product = c_product*c(ii) + enddo + + ! compute coefficient + + Tinv(i,j) = (-1)**(i+j) * c_product * theta(j-1) * phi(i+1) / detT + + endif ! i >= j + + enddo ! i + enddo ! j + + + end subroutine tridiagonal_inverse + +end module shr_matrix_mod diff --git a/share/csm_share/test/unit/CMakeLists.txt b/share/csm_share/test/unit/CMakeLists.txt index f4f75b91af9..eddd31964dd 100644 --- a/share/csm_share/test/unit/CMakeLists.txt +++ b/share/csm_share/test/unit/CMakeLists.txt @@ -19,3 +19,5 @@ add_subdirectory(shr_vmath_test) add_subdirectory(shr_wv_sat_test) add_subdirectory(shr_precip_test) + +add_subdirectory(shr_matrix_test) diff --git a/share/csm_share/test/unit/shr_matrix_test/CMakeLists.txt b/share/csm_share/test/unit/shr_matrix_test/CMakeLists.txt new file mode 100644 index 00000000000..561978073ff --- /dev/null +++ b/share/csm_share/test/unit/shr_matrix_test/CMakeLists.txt @@ -0,0 +1,20 @@ +# Local pFUnit files. +set(pf_sources + test_tridiagonal_inverse.pf) + +# Sources to test. +set(sources_needed + shr_assert_mod.F90 + shr_infnan_mod.F90 + shr_kind_mod.F90 + shr_log_mod.F90 + shr_matrix_mod.F90 + shr_strconvert_mod.F90 + shr_sys_mod.nompi_abortthrows.F90) +extract_sources("${sources_needed}" "${share_sources}" test_sources) + +# Do source preprocessing and add the executable. +create_pFUnit_test(shr_matrix_mod shr_matrix_mod_exe + "${pf_sources}" "${test_sources}") + +declare_generated_dependencies(shr_matrix_mod_exe "${share_genf90_sources}") \ No newline at end of file diff --git a/share/csm_share/test/unit/shr_matrix_test/test_tridiagonal_inverse.pf b/share/csm_share/test/unit/shr_matrix_test/test_tridiagonal_inverse.pf new file mode 100644 index 00000000000..0ca509c1283 --- /dev/null +++ b/share/csm_share/test/unit/shr_matrix_test/test_tridiagonal_inverse.pf @@ -0,0 +1,88 @@ +module test_tridiagonal_inverse + + ! Tests of shr_matrix_mod: tridiagonal_inverse + + use pfunit_mod + use shr_matrix_mod + use shr_kind_mod , only : r8 => shr_kind_r8 + use shr_infnan_mod, only : nan => shr_infnan_nan, assignment(=) + + implicit none + + @TestCase + type, extends(TestCase) :: TestTridiagInverse + contains + procedure :: setUp + procedure :: tearDown + end type TestTridiagInverse + + real(r8), parameter :: tol = 1.e-13_r8 + +contains + + subroutine setUp(this) + class(TestTridiagInverse), intent(inout) :: this + end subroutine setUp + + subroutine tearDown(this) + class(TestTridiagInverse), intent(inout) :: this + end subroutine tearDown + + function tridiagonal_matrix(a, b, c) + ! Construct a tridiagonal matrix from vectors + real(r8), intent(in) :: a(:) ! Center diagonal + real(r8), intent(in) :: b(:) ! Upper diagonal (superdiagonal); b(n) is ignored + real(r8), intent(in) :: c(:) ! Lower diagonal (subdiagonal); c(n) is ignored + real(r8) :: tridiagonal_matrix(size(a), size(a)) + + integer :: i + integer :: n + + n = size(a) + @assertEqual(n, size(b)) + @assertEqual(n, size(c)) + + tridiagonal_matrix(:,:) = 0._r8 + do i = 1, n + tridiagonal_matrix(i, i) = a(i) + end do + do i = 1, n-1 + tridiagonal_matrix(i, i+1) = b(i) + end do + do i = 1, n-1 + tridiagonal_matrix(i+1, i) = c(i) + end do + end function tridiagonal_matrix + + function identity_matrix(n) + ! Returns the identity matrix of size n x n + real(r8) :: identity_matrix(n,n) + integer, intent(in) :: n + integer :: i + + identity_matrix(:,:) = 0._r8 + do i = 1, n + identity_matrix(i,i) = 1._r8 + end do + end function identity_matrix + + @Test + subroutine basic(this) + ! Do a basic test of tridiagonal_inverse + class(TestTridiagInverse), intent(inout) :: this + real(r8), parameter :: a(5) = [5._r8, 4._r8, 3._r8, 2._r8, 1._r8] + real(r8) :: b(5) = [17._r8, 2._r8, 4._r8, 3._r8, 0._r8] + real(r8) :: c(5) = [1._r8, 2._r8, 4._r8, 3._r8, 0._r8] + real(r8) :: arr(5,5) + real(r8) :: Tinv(5,5) + + b(5) = nan + c(5) = nan + + call tridiagonal_inverse(a, b, c, Tinv) + + arr = tridiagonal_matrix(a, b, c) + @assertEqual(identity_matrix(5), matmul(arr, Tinv), tolerance=tol) + end subroutine basic + +end module test_tridiagonal_inverse From 706e52bad96f96cf0e7684e564849616b14ed759 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 27 Apr 2016 05:43:45 -0600 Subject: [PATCH 20/61] Change tridiagonal inverse test to have more unique values Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- .../test/unit/shr_matrix_test/test_tridiagonal_inverse.pf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/csm_share/test/unit/shr_matrix_test/test_tridiagonal_inverse.pf b/share/csm_share/test/unit/shr_matrix_test/test_tridiagonal_inverse.pf index 0ca509c1283..864df4f1898 100644 --- a/share/csm_share/test/unit/shr_matrix_test/test_tridiagonal_inverse.pf +++ b/share/csm_share/test/unit/shr_matrix_test/test_tridiagonal_inverse.pf @@ -70,8 +70,8 @@ contains subroutine basic(this) ! Do a basic test of tridiagonal_inverse class(TestTridiagInverse), intent(inout) :: this - real(r8), parameter :: a(5) = [5._r8, 4._r8, 3._r8, 2._r8, 1._r8] - real(r8) :: b(5) = [17._r8, 2._r8, 4._r8, 3._r8, 0._r8] + real(r8), parameter :: a(5) = [10._r8, 9._r8, 8._r8, 7._r8, 6._r8] + real(r8) :: b(5) = [11._r8, 12._r8, 14._r8, 13._r8, 0._r8] real(r8) :: c(5) = [1._r8, 2._r8, 4._r8, 3._r8, 0._r8] real(r8) :: arr(5,5) real(r8) :: Tinv(5,5) From d82881894498c253d3c6abd11f7cf3a0fdf519c5 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 27 Apr 2016 12:59:55 -0600 Subject: [PATCH 21/61] Add a script to plot gradients Also add an example input file Test suite: None Test baseline: N/A Test namelist changes: N/A Test status: N/A Fixes: None User interface changes?: No Code review: None --- .../gradient_example.txt | 5 + .../plot_gradient | 163 ++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 driver_cpl/unit_test/vertical_gradient_calculator_test/gradient_example.txt create mode 100755 driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/gradient_example.txt b/driver_cpl/unit_test/vertical_gradient_calculator_test/gradient_example.txt new file mode 100644 index 00000000000..70d70dc9d0c --- /dev/null +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/gradient_example.txt @@ -0,0 +1,5 @@ +3 +0 10 20 30 +5 15 25 +-3 7 15 +2 1 3 diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient b/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient new file mode 100755 index 00000000000..1a758153b82 --- /dev/null +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient @@ -0,0 +1,163 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import sys +import os.path + +if sys.hexversion < 0x02070000: + print(70 * "*") + print("ERROR: {0} requires python >= 2.7.x. ".format(sys.argv[0])) + print("It appears that you are running python {0}".format( + ".".join(str(x) for x in sys.version_info[0:3]))) + print(70 * "*") + sys.exit(1) + +import argparse +import matplotlib.pyplot as plt +import matplotlib.pylab as pylab + +class GradientInfo: + + def __init__(self, nelev, elevclass_bounds, topo, field, gradient): + """Create a GradientInfo object + + nelev: int + elevclass_bounds: tuple of (nelev+1) floats + topo: tuple of (nelev) floats + field: tuple of (nelev) floats + gradient: tuple of (nelev) floats + """ + + self.nelev = nelev + + if (len(elevclass_bounds) != nelev+1): + raise ValueError('elevclass_bounds should be of size nelev+1') + self.elevclass_bounds = elevclass_bounds + + if (len(topo) != nelev): + raise ValueError('topo should be of size nelev') + self.topo = topo + + if (len(field) != nelev): + raise ValueError('topo should be of size nelev') + self.field = field + + if (len(gradient) != nelev): + raise ValueError('gradients should be of size nelev') + self.gradient = gradient + + @classmethod + def from_file(cls, filename): + """Create a GradientInfo object by reading a file + + File should be formatted as: + nelev (int) + elevclass_bounds (list of floats; length nelev+1) + topo (list of floats; length nelev) + field (list of floats; length nelev) + gradient (list of floats; length nelev) + + For example: + 3 + 0. 10. 20. 30. + 5. 15. 25. + -3. 7. 15. + 2. 1. 3. + """ + + with open(filename) as f: + nelev = int(f.readline()) + elevclass_bounds = [float(x) for x in f.readline().split()] + topo = [float(x) for x in f.readline().split()] + field = [float(x) for x in f.readline().split()] + gradient = [float(x) for x in f.readline().split()] + + return cls(nelev, elevclass_bounds, topo, field, gradient) + + def draw_figure(self, output_filename): + """Draw a figure of this gradient info, and save it to + output_filename""" + + elev_min = min(self.elevclass_bounds) + elev_max = max(self.elevclass_bounds) + field_min = min(self.field) + field_max = max(self.field) + + y_range = field_max - field_min + y_max = field_max + 0.2 * y_range + y_min = field_min - 0.2 * y_range + + plt.axis([elev_min, elev_max, y_min, y_max]) + plt.plot(self.topo, self.field, 'ro') + + for ec_bound in self.elevclass_bounds: + plt.plot([ec_bound, ec_bound], [y_min, y_max], 'k') + + for ec in range(self.nelev): + (xs, ys) = gradient_line(self.topo[ec], self.field[ec], self.gradient[ec], + self.elevclass_bounds[ec], self.elevclass_bounds[ec+1]) + plt.plot(xs, ys, 'b') + + pylab.savefig(output_filename) + plt.close() + + +def commandline_options(): + """Process command-line arguments""" + + parser = argparse.ArgumentParser( + description = 'Creates plots of gradients from one or more input files', + epilog = """Each file is expected to be formatted as follows: + nelev (int) + elevclass_bounds (list of floats; length nelev+1) + topo (list of floats; length nelev) + field (list of floats; length nelev) + gradient (list of floats; length nelev) + + For example: + 3 + 0. 10. 20. 30. + 5. 15. 25. + -3. 7. 15. + 2. 1. 3.""" + ) + + parser.add_argument('files', nargs='+', + help='names of file(s) containing gradients to plot') + + parser.add_argument('--backtrace', action='store_true', + help='show exception backtraces as extra debugging output') + + options = parser.parse_args() + return options + +def gradient_line(x, y, slope, x_lb, x_ub): + """Returns two tuples (x1, x2), (y1, y2) giving the end points of a line + that: + + - Has center (x, y) + - Has slope 'slope' + - Has x coordinates going from x_lb to x_ub + """ + + y_lb = y + (x_lb - x)*slope + y_ub = y + (x_ub - x)*slope + return ((x_lb, x_ub), (y_lb, y_ub)) + +def main(options): + for input_filename in options.files: + file_base = os.path.splitext(input_filename)[0] + gradient_info = GradientInfo.from_file(input_filename) + gradient_info.draw_figure(file_base + '.pdf') + +if __name__ == "__main__": + options = commandline_options() + try: + status = main(options) + sys.exit(status) + except Exception as error: + print(str(error)) + if options.backtrace: + traceback.print_exc() + sys.exit(1) From 8a63e33fe033130472b325968cb1ee001b0dc654 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 27 Apr 2016 13:59:06 -0600 Subject: [PATCH 22/61] Add vertical_gradient_calculator_continuous Not yet tested at all Test suite: None Test baseline: N/A Test namelist changes: N/A Test status: N/A Fixes: None User interface changes?: No Code review: None --- driver_cpl/driver/CMakeLists.txt | 1 + ...vertical_gradient_calculator_2nd_order.F90 | 41 +- .../vertical_gradient_calculator_base.F90 | 38 ++ ...ertical_gradient_calculator_continuous.F90 | 437 ++++++++++++++++++ .../CMakeLists.txt | 1 + ...vertical_gradient_calculator_continuous.pf | 30 ++ 6 files changed, 517 insertions(+), 31 deletions(-) create mode 100644 driver_cpl/driver/vertical_gradient_calculator_continuous.F90 create mode 100644 driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf diff --git a/driver_cpl/driver/CMakeLists.txt b/driver_cpl/driver/CMakeLists.txt index 52d7fab2ee0..676b749e0b9 100644 --- a/driver_cpl/driver/CMakeLists.txt +++ b/driver_cpl/driver/CMakeLists.txt @@ -6,6 +6,7 @@ list(APPEND drv_sources seq_map_type_mod.F90 vertical_gradient_calculator_base.F90 vertical_gradient_calculator_2nd_order.F90 + vertical_gradient_calculator_continuous.F90 ) sourcelist_to_parent(drv_sources) \ No newline at end of file diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index 17c9fae5a42..43e1e57723c 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -39,7 +39,6 @@ module vertical_gradient_calculator_2nd_order contains procedure :: calc_vertical_gradient - procedure, private :: check_elevclass_bounds ! check for monotonicity of elevclass_bounds procedure, private :: set_data_from_attr_vect ! extract data from an attribute vector procedure, private :: check_topo ! check topographic heights procedure, private :: limit_gradient @@ -106,7 +105,10 @@ function constructor(attr_vect, fieldname, toponame, & this%max_elevation_class = max_elevation_class allocate(this%elevclass_bounds((min_elevation_class-1):max_elevation_class)) this%elevclass_bounds(:) = elevclass_bounds(:) - call this%check_elevclass_bounds() + + ! (In principle, we could also handle monotonically decreasing elevclass_bounds, but + ! that would require generalizing some code, such as in check_topo.) + call this%check_elevclass_bounds_monotonic_increasing(this%elevclass_bounds) call this%set_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names) @@ -207,35 +209,6 @@ subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) end subroutine calc_vertical_gradient - !----------------------------------------------------------------------- - subroutine check_elevclass_bounds(this) - ! - ! !DESCRIPTION: - ! Ensure that elevclass_bounds are monotonically increasing; abort if there is a - ! problem - ! - ! (In principle, we could also handle monotonically decreasing elevclass_bounds, but - ! that would require generalizing some code, such as in check_topo.) - ! - ! !ARGUMENTS: - class(vertical_gradient_calculator_2nd_order_type), intent(in) :: this - ! - ! !LOCAL VARIABLES: - integer :: i - - character(len=*), parameter :: subname = 'check_elevclass_bounds' - !----------------------------------------------------------------------- - - do i = this%min_elevation_class, this%max_elevation_class - if (this%elevclass_bounds(i-1) >= this%elevclass_bounds(i)) then - write(logunit,*) subname, ': ERROR: elevclass_bounds must be monotonically increasing' - write(logunit,*) 'elevclass_bounds = ', this%elevclass_bounds - call shr_sys_abort(subname//': ERROR: elevclass_bounds must be monotonically increasing') - end if - end do - - end subroutine check_elevclass_bounds - !----------------------------------------------------------------------- subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame, elevclass_names) ! @@ -244,6 +217,12 @@ subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame, elevcla ! ! Sets this%num_points, and allocates and sets this%field and this%topo. ! + ! TODO(wjs, 2016-04-26) The current flow is that the constructor calls this + ! routine. It could be better to move this routine into a factory class that creates + ! objects by (1) calling this routine to extract fields from the attribute vector, and + ! then (2) calling the constructor of this class using these extracted data (so the + ! constructor would never need to be passed an attribute vector). + ! ! !USES: use mct_mod ! diff --git a/driver_cpl/driver/vertical_gradient_calculator_base.F90 b/driver_cpl/driver/vertical_gradient_calculator_base.F90 index 7573a636465..1261ecb9133 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_base.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_base.F90 @@ -7,7 +7,9 @@ module vertical_gradient_calculator_base ! This module defines an abstract base class for computing the vertical gradient of a ! field. + use seq_comm_mct, only : logunit use shr_kind_mod, only : r8 => shr_kind_r8 + use shr_sys_mod, only : shr_sys_abort implicit none private @@ -18,6 +20,12 @@ module vertical_gradient_calculator_base contains ! Calculate the vertical gradient for all points, for a given elevation class procedure(calc_vertical_gradient_interface), deferred :: calc_vertical_gradient + + ! These routines are utility methods for derived classes; they should not be called + ! by clients of this class. + procedure, nopass :: check_elevclass_bounds_monotonic_increasing + + end type vertical_gradient_calculator_base_type abstract interface @@ -32,4 +40,34 @@ subroutine calc_vertical_gradient_interface(this, elevation_class, vertical_grad end subroutine calc_vertical_gradient_interface end interface +contains + + !----------------------------------------------------------------------- + subroutine check_elevclass_bounds_monotonic_increasing(elevclass_bounds) + ! + ! !DESCRIPTION: + ! Ensure that elevclass_bounds are monotonically increasing; abort if there is a + ! problem + ! + ! !ARGUMENTS: + real(r8), intent(in) :: elevclass_bounds(:) + ! + ! !LOCAL VARIABLES: + integer :: i + + character(len=*), parameter :: subname = 'check_elevclass_bounds' + !----------------------------------------------------------------------- + + do i = 2, size(elevclass_bounds) + if (elevclass_bounds(i-1) >= elevclass_bounds(i)) then + write(logunit,*) subname, ': ERROR: elevclass_bounds must be monotonically increasing' + write(logunit,*) 'elevclass_bounds = ', elevclass_bounds + call shr_sys_abort(subname//': ERROR: elevclass_bounds must be monotonically increasing') + end if + end do + + end subroutine check_elevclass_bounds_monotonic_increasing + + + end module vertical_gradient_calculator_base diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 new file mode 100644 index 00000000000..37abb07c72c --- /dev/null +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -0,0 +1,437 @@ +module vertical_gradient_calculator_continuous + + !--------------------------------------------------------------------- + ! + ! Purpose: + ! + ! This module defines a subclass of vertical_gradient_calculator_base_type for + ! computing piecewise continuous vertical gradients using a matrix solve. + +#include "shr_assert.h" + use seq_comm_mct, only : logunit + use vertical_gradient_calculator_base, only : vertical_gradient_calculator_base_type + use shr_kind_mod, only : r8 => shr_kind_r8 + use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) + use mct_mod + use shr_log_mod, only : errMsg => shr_log_errMsg + use shr_sys_mod, only : shr_sys_abort + + implicit none + private + + public :: vertical_gradient_calculator_continuous_type + + type, extends(vertical_gradient_calculator_base_type) :: & + vertical_gradient_calculator_continuous_type + private + + integer :: min_elevation_class + integer :: max_elevation_class + integer :: nelev ! number of elevation classes + integer :: num_points + real(r8), allocatable :: field(:,:) ! field(i,j) is elevation class i, field j + real(r8), allocatable :: topo(:,:) ! topo(i,j) is elevation class i, field j + + real(r8), allocatable :: vertical_gradient(:,:) ! precomputed vertical gradients; vertical_gradient(i,j) is elevation class i, field j + + ! Bounds of each elevation class. This array has one more element than the number of + ! elevation classes, since it contains lower and upper bounds for each elevation + ! class. The indices go (min_elevation_class-1):max_elevation_class. These bounds + ! are guaranteed to be monotonically increasing. + real(r8), allocatable :: elevclass_bounds(:) + + contains + procedure :: calc_vertical_gradient + + procedure, private :: set_data_from_attr_vect ! extract data from an attribute vector + procedure, private :: precompute_vertical_gradients ! compute vertical gradients for all ECs + + end type vertical_gradient_calculator_continuous_type + + interface vertical_gradient_calculator_continuous_type + module procedure constructor + end interface vertical_gradient_calculator_continuous_type + +contains + + !----------------------------------------------------------------------- + function constructor(attr_vect, fieldname, toponame, & + nelev, elevclass_names, & + elevclass_bounds) & + result(this) + ! + ! !DESCRIPTION: + ! Creates a vertical_gradient_calculator_continuous_type object by reading the + ! necessary data from the provided attribute vector. + ! + ! Pre-condition: elevclass_bounds must be monotonically increasing. + ! + ! Pre-condition: Topographic heights in the attribute vector should all lie inside the + ! bounds of their respective elevation class (given by elevclass_bounds), with the + ! possible exception of the lowest elevation class (topographic heights can lie below + ! the arbitrary lower bound of the elevation class) and the highest elevation class + ! (topographic heights can lie above the arbitrary upper bound of the elevation + ! class). For grid cells where this is not true, sets vertical gradient to 0 for all + ! elevation classes. + ! + ! The attribute vector is assumed to have fields named fieldname // + ! elevclass_names(1), toponame // elevclass_names(1), etc. + ! + ! !USES: + ! + ! !ARGUMENTS: + type(vertical_gradient_calculator_continuous_type) :: this ! function result + type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data + character(len=*) , intent(in) :: fieldname ! base name of the field of interest + character(len=*) , intent(in) :: toponame ! base name of the topographic field + integer , intent(in) :: nelev ! number of elevation classes (indexing assumed to start at 1) + + ! strings corresponding to each elevation class + character(len=*) , intent(in) :: elevclass_names(:) + + ! bounds of each elevation class; this array should have one more element than the + ! number of elevation classes, since it contains lower and upper bounds for each + ! elevation class + real(r8) , intent(in) :: elevclass_bounds(0:) + ! + ! !LOCAL VARIABLES: + integer :: pt + + character(len=*), parameter :: subname = 'constructor' + !----------------------------------------------------------------------- + + SHR_ASSERT_ALL((ubound(elevclass_names) == (/nelev/)), errMsg(__FILE__, __LINE__)) + SHR_ASSERT_ALL((ubound(elevclass_bounds) == (/nelev/)), errMsg(__FILE__, __LINE__)) + + this%nelev = nelev + allocate(this%elevclass_bounds(0:nelev)) + this%elevclass_bounds(:) = elevclass_bounds(:) + call this%check_elevclass_bounds_monotonic_increasing(this%elevclass_bounds) + + call this%set_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names) + + allocate(this%vertical_gradient(this%nelev, this%num_points)) + this%vertical_gradient(:,:) = nan + + ! FIXME(wjs, 2016-04-26) Uncomment this call to check_topo - but change it so that it + ! sets a flag, and then we'll set vertical gradients to 0 wherever topos are bad. + ! + ! call this%check_topo() + + ! For this implementation of the vertical gradient calculator, we compute all vertical + ! gradients in object construction. This is because we compute them all simultaneously + ! rather than independently. (So then, the call to the routine that would normally + ! compute vertical gradients for one elevation class simply returns the pre-computed + ! vertical gradients for that elevation class.) + + do pt = 1, this%num_points + call this%precompute_vertical_gradients(pt) + end do + + end function constructor + + !----------------------------------------------------------------------- + subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) + ! + ! !DESCRIPTION: + ! Returns the vertical gradient for all points, at a given elevation class. + ! + ! !USES: + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_continuous_type), intent(in) :: this + integer, intent(in) :: elevation_class + + ! vertical_gradient should already be allocated to the appropriate size + real(r8), intent(out) :: vertical_gradient(:) + ! + ! !LOCAL VARIABLES: + + character(len=*), parameter :: subname = 'calc_vertical_gradient' + !----------------------------------------------------------------------- + + SHR_ASSERT((size(vertical_gradient) == this%num_points), errMsg(__FILE__, __LINE__)) + + if (elevation_class < 1 .or. & + elevation_class > this%nelev) then + write(logunit,*) subname, ': ERROR: elevation class out of bounds: ', & + elevation_class, this%nelev + call shr_sys_abort(subname//': ERROR: elevation class out of bounds') + end if + + vertical_gradient(:) = this%vertical_gradient(elevation_class, :) + + end subroutine calc_vertical_gradient + + + !----------------------------------------------------------------------- + subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame, elevclass_names) + ! + ! !DESCRIPTION: + ! Extract data from an attribute vector. + ! + ! Sets this%num_points, and allocates and sets this%field and this%topo. + ! + ! TODO(wjs, 2016-04-26) The current flow is that the constructor calls this + ! routine. It could be better to move this routine into a factory class that creates + ! objects by (1) calling this routine to extract fields from the attribute vector, and + ! then (2) calling the constructor of this class using these extracted data (so the + ! constructor would never need to be passed an attribute vector). + ! + ! !USES: + use mct_mod + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_continuous_type), intent(inout) :: this + type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data + character(len=*) , intent(in) :: fieldname ! base name of the field of interest + character(len=*) , intent(in) :: toponame ! base name of the topographic field + character(len=*) , intent(in) :: elevclass_names(:) ! strings corresponding to each elevation class + ! + ! !LOCAL VARIABLES: + integer :: elevclass + character(len=:), allocatable :: fieldname_ec + character(len=:), allocatable :: toponame_ec + + ! The following temporary array is needed because mct wants pointers + real(r8), pointer :: temp(:) + + character(len=*), parameter :: subname = 'set_data_from_attr_vect' + !----------------------------------------------------------------------- + + this%num_points = mct_aVect_lsize(attr_vect) + + allocate(this%field(this%nelev, this%num_points)) + allocate(this%topo(this%nelev, this%num_points)) + allocate(temp(this%num_points)) + + do elevclass = 1, this%nelev + fieldname_ec = trim(fieldname) // trim(elevclass_names(elevclass)) + call mct_aVect_exportRattr(attr_vect, fieldname_ec, temp) + this%field(elevclass,:) = temp(:) + + toponame_ec = trim(toponame) // trim(elevclass_names(elevclass)) + call mct_aVect_exportRattr(attr_vect, toponame_ec, temp) + this%topo(elevclass,:) = temp(:) + end do + + deallocate(temp) + + end subroutine set_data_from_attr_vect + + !----------------------------------------------------------------------- + subroutine precompute_vertical_gradients(this, pt) + ! + ! !DESCRIPTION: + ! Compute and save vertical gradients for all elevation classes. + ! + ! Computes a gradient in each elevation class such that the field is continuous at + ! interfaces and the sum over squared differences from the mean is minimized. + ! + ! !USES: + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_continuous_type), intent(inout) :: this + integer, intent(in) :: pt ! point to compute gradients for (1..this%num_points) + ! + ! !LOCAL VARIABLES: + real(r8) :: field(this%nelev) ! mean field value of each elevation class + real(r8) :: topo(this%nelev) ! mean topo of each elevation class + real(r8) :: grad(this%nelev) ! computed gradient + real(r8) :: topo_interface(0:this%nelev) ! elevations at interfaces between classes + real(r8) :: dl(this%nelev) ! lower 1/2 widths of elevation classes + real(r8) :: du(this%nelev) ! upper 1/2 widths of elevation classes + real(r8) :: h_lo(this%nelev) ! lower bounds for computing norms + real(r8) :: h_hi(this%nelev) ! upper bounds for computing norms + real(r8) :: dgrad(this%nelev) ! grad - grad_mean + real(r8) :: weight_grad(this%nelev) ! weight for dgrad in solution + real(r8) :: grad_mean ! mean value of gradient + + real(r8) :: diag(this%nelev-1) ! diagonal of tridiagonal matrix + real(r8) :: subd(this%nelev-1) ! subdiagonal of tridiagonal matrix + real(r8) :: supd(this%nelev-1) ! superdiagonal of tridiagonal matrix + + real(r8) :: b(this%nelev-1) ! rhs in A*x = b + real(r8) :: A(this%nelev-1, this%nelev) ! matrix in A*x = b + real(r8) :: A_AT(this%nelev-1, this%nelev-1) ! A * (transpose of A) + real(r8) :: Tinv(this%nelev-1, this%nelev-1) ! inverse of tridiagonal matrix T = A * AT + real(r8) :: AT_Tinv(this%nelev, this%nelev-1) ! (transpose of A) * Tinv + real(r8) :: x_least_norm(this%nelev) ! least-norm solution x in A*x = b + + ! FIXME(wjs, 2016-04-27) Rename to nelev, or probably just delete + integer :: n + + ! FIXME(wjs, 2016-04-26) Rename to ec + integer :: i + + + character(len=*), parameter :: subname = 'precompute_vertical_gradients' + !----------------------------------------------------------------------- + + field(:) = this%field(:,pt) + topo(:) = this%topo(:,pt) + + ! FIXME(wjs, 2016-04-27) Rename to elevclass_bounds? Update: just delete this + ! temporary variable. + topo_interface(:) = this%elevclass_bounds(:) + + n = this%nelev + + do i = 1, n + dl(i) = topo(i) - topo_interface(i-1) ! dl(1) is never used + du(i) = topo_interface(i) - topo(i) ! du(n) is never used + end do + + ! FIXME(wjs, 2016-04-26) Extract method for this loop: returns weight_grad in each + ! elevation class + do i = 1, n + + if (i == 1) then + ! If topo(1) is near the top of EC1, then the weight just includes twice the + ! width of [topo(i) .. topo_interface(i)] + h_lo(i) = max(topo_interface(i-1), (topo(i) - (topo_interface(i) - topo(i)))) + else + h_lo(i) = topo_interface(i-1) + end if + + if (i == n) then + ! If topo(n) is near the bottom of EC N, then the weight just includes twice + ! the width of [topo_interface(i-1) .. topo(i)] + h_hi(i) = min(topo_interface(i), (topo(i) + (topo(i) - topo_interface(i-1)))) + else + h_hi(i) = topo_interface(i) + end if + + ! set gradient weights based on h_hi - h_lo in each class + weight_grad(i) = (h_hi(i) - h_lo(i)) / (h_hi(n) - h_lo(1)) + + ! FIXME(wjs, 2016-04-26) Check that weight_grad > 0. + ! If weight_grad is just slightly > 0, the matrix will be poorly conditioned. + + end do + + !-------------------------------------------------------------------- + ! Set up matrix problem for gradient solution. + ! The idea is to match field values at interfaces. + ! + ! For each class n: + ! field(n) + du(n)*grad(n) = field(n+1) - dl(n+1)*grad(n+1) + ! + ! Rearrange to get + ! du(n)*grad(n) + dl(n+1)*grad(n+1) = field(n+1) - field(n) + ! + ! This is a bidiagonal matrix system: + ! + ! | du(1) dl(2) | | grad(1) | | field(2) - field(1) | + ! | du(2) dl(3) | | grad(2) | | field(3) - field(2) | + ! | du(3) dl(4) | * | grad(3) | = | field(4) - field(3) | + ! | du(4) dl(5) | | grad(4) | | field(5) - field(4) | + ! | grad(5) | + ! + ! The solution is underdetermined (4 equations, 5 unknowns). + ! So we add an additional constraint: + ! Minimize the norm of the difference between the gradient in each class and the + ! mean gradient, weighted by the range of the elevation class. + ! That is, minimize the sum over i of (wt(i) * (grad(i) - grad_mean))^2. + ! + ! The mean gradient is given by + ! + ! field(n) - field(1) + ! grad_mean = __________________ + ! topo(n) - topo(1) + ! + ! The weights are + ! + ! h_hi(i) - h_lo(i) + ! wt(i) = _________________ + ! h_hi(n) - h_lo(1) + ! + ! Putting in the weights and rearranging, we get + ! + ! | wt(1) * dgrad(1) | | field(2) - field(1) | | wt(1) * grad_mean | + ! | wt(2) * dgrad(2) | | field(3) - field(2) | | wt(2) * grad_mean | + ! A * | wt(3) * dgrad(3) | = | field(4) - field(3) | - A * | wt(3) * grad_mean | + ! | wt(4) * dgrad(4) | | field(5) - field(4) | | wt(4) * grad_mean | + ! | wt(5) * dgrad(5) | | wt(5) * grad_mean | + ! + ! where A is the bidiagonal matrix above, adjusted to include the gradient weights. + ! E.g., du(1) becomes du(1)/wt(1), etc. + ! This system is in the form A*x = b, where we want to solve for x. + ! + ! It can be shown that the least-norm solution of A*x = b is given by + ! + ! x = A^T (A*A^T)^{-1} b + ! + ! So given A, we need to compute (A*A^T), takes its inverse, premultiply by A^T, + ! and multiply the result by b. + ! Given the least-norm solution, it is straightforward to compute the gradients. + !-------------------------------------------------------------------- + + ! Fill A + ! A has (n-1) rows and n columns + + A(:,:) = 0._r8 + do i = 1, n-1 + A(i,i) = du(i) / weight_grad(i) + A(i,i+1) = dl(i+1) / weight_grad(i+1) + end do + + ! Compute A * A^T, a tridiagonal matrix of size (n-1) + + A_AT = matmul(A, transpose(A)) + + ! Compute tridiagonal entries of (A * A^T) + + do i = 1, n-1 + diag(i) = A_AT(i,i) ! diagonal + if (i < n-1) then + supd(i) = A_AT(i,i+1) ! superdiagonal + subd(i) = A_AT(i+1,i) ! subdiagonal + else + supd(i) = 0._r8 + subd(i) = 0._r8 + end if + end do + + ! Compute inverse of (A * A^T) + ! Tinv has size (n-1,n-1) + + call tridiagonal_inverse(diag, supd, subd, Tinv) + + ! Premultiply by A^T + ! A^T * Tinv has n rows and (n-1) columns + + AT_Tinv = matmul(transpose(A), Tinv) + + ! Compute the rhs vector b + ! Start with the field differences + + do i = 1, n-1 + b(i) = field(i+1) - field(i) + enddo + + ! Compute mean gradient + + grad_mean = (field(n) - field(1)) / (topo(n) - topo(1)) + + ! Subtract (A * weight_grad*grad_mean), a vector of size(n-1) + + b(:) = b(:) - matmul(A, weight_grad(:)*grad_mean) + + ! Multiply AT_Tinv by b to get the least-norm solution + ! b has size (n-1), x_least_norm has size n + + x_least_norm = matmul(AT_Tinv, b) + + ! Divide by the weighting factor to get dgrad + dgrad(:) = x_least_norm(:) / weight_grad(:) + + ! Add dgrad to the mean to get the total gradient + grad(:) = grad_mean + dgrad(:) + + this%vertical_gradient(:,pt) = grad(:) + + end subroutine precompute_vertical_gradients + + +end module vertical_gradient_calculator_continuous diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt index d8ced7f8201..946e3a324b0 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt @@ -1,5 +1,6 @@ set (pfunit_sources test_vertical_gradient_calculator_2nd_order.pf + test_vertical_gradient_calculator_continuous.pf ) create_pFUnit_test(vertical_gradient_calculator vertical_gradient_calculator_exe diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf new file mode 100644 index 00000000000..a636255c38c --- /dev/null +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -0,0 +1,30 @@ +module test_vertical_gradient_calculator_continuous + + ! Tests of vertical_gradient_calculator_continuous + + use pfunit_mod + use vertical_gradient_calculator_continuous + use shr_kind_mod , only : r8 => shr_kind_r8 + + implicit none + + @TestCase + type, extends(TestCase) :: TestVertGradCalcCont + contains + procedure :: setUp + procedure :: tearDown + end type TestVertGradCalcCont + + real(r8), parameter :: tol = 1.e-13_r8 + +contains + + subroutine setUp(this) + class(TestVertGradCalcCont), intent(inout) :: this + end subroutine setUp + + subroutine tearDown(this) + class(TestVertGradCalcCont), intent(inout) :: this + end subroutine tearDown + +end module test_vertical_gradient_calculator_continuous From d280f3b933a211264a78666d433037c42f34248f Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 27 Apr 2016 14:21:15 -0600 Subject: [PATCH 23/61] Move some shared test utility functions to a new file Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- .../CMakeLists.txt | 7 +- ..._vertical_gradient_calculator_2nd_order.pf | 67 +------------------ .../test_vertical_gradient_test_utils.pf | 61 +++++++++++++++++ .../vertical_gradient_test_utils.F90 | 60 +++++++++++++++++ 4 files changed, 129 insertions(+), 66 deletions(-) create mode 100644 driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_test_utils.pf create mode 100644 driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt index 946e3a324b0..09b2369c075 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt @@ -1,9 +1,14 @@ set (pfunit_sources + test_vertical_gradient_test_utils.pf test_vertical_gradient_calculator_2nd_order.pf test_vertical_gradient_calculator_continuous.pf ) +set (extra_sources + vertical_gradient_test_utils.F90 + ) + create_pFUnit_test(vertical_gradient_calculator vertical_gradient_calculator_exe - "${pfunit_sources}" "") + "${pfunit_sources}" "${extra_sources}") target_link_libraries(vertical_gradient_calculator_exe ${DRV_UNIT_TEST_LIBS}) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index fa2fb683116..6b63c12f8ab 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -9,6 +9,7 @@ module test_vertical_gradient_calculator_2nd_order use mct_mod, only : mct_aVect, mct_aVect_clean use mct_wrapper_mod, only : mct_init, mct_clean use avect_wrapper_mod + use vertical_gradient_test_utils implicit none @@ -20,7 +21,6 @@ module test_vertical_gradient_calculator_2nd_order contains procedure :: setUp procedure :: tearDown - procedure :: create_av procedure :: create_calculator procedure :: calculateAndVerifyGradient_1point_ECmid end type TestVertGradCalc2ndOrder @@ -41,47 +41,6 @@ contains call mct_clean() end subroutine tearDown - function two_digit_string(val) - ! Converts val to a two-digit string - character(len=2) :: two_digit_string - integer, intent(in) :: val - - write(two_digit_string, '(i2.2)') val - end function two_digit_string - - subroutine create_av(this, topo, data, toponame, dataname) - ! Creates the attribute vector in 'this' - class(TestVertGradCalc2ndOrder), intent(inout) :: this - real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j - real(r8), intent(in) :: data(:,:) ! data(i,j) is point i, elevation class j - character(len=*), intent(in) :: toponame - character(len=*), intent(in) :: dataname - - integer :: npts - integer :: n_elev_classes - integer :: elevclass - character(len=64), allocatable :: attr_tags(:) - - npts = size(topo, 1) - @assertEqual(npts, size(data, 1)) - n_elev_classes = size(topo, 2) - @assertEqual(n_elev_classes, size(data, 2)) - - allocate(attr_tags(2*n_elev_classes)) - do elevclass = 1, n_elev_classes - attr_tags(elevclass) = dataname // two_digit_string(elevclass) - end do - do elevclass = 1, n_elev_classes - attr_tags(n_elev_classes + elevclass) = toponame // two_digit_string(elevclass) - end do - - call create_aVect_with_data_rows_are_points(this%av, & - attr_tags = attr_tags, & - data = reshape([data, topo], [npts, n_elev_classes * 2])) - - end subroutine create_av - - function create_calculator(this, topo, data, elevclass_bounds) & result(calculator) type(vertical_gradient_calculator_2nd_order_type) :: calculator @@ -105,7 +64,7 @@ contains elevclass_names(i) = two_digit_string(i) end do - call this%create_av(topo, data, 'topo', 'data') + call create_av(topo, data, 'topo', 'data', this%av) calculator = vertical_gradient_calculator_2nd_order_type( & attr_vect = this%av, & @@ -152,28 +111,6 @@ contains @assertEqual(expected_vertical_gradient, vertical_gradient(1), tolerance=tol, message = msg) end subroutine calculateAndVerifyGradient_1point_ECmid - @Test - subroutine test_create_av(this) - ! Tests the create_av helper routine - class(TestVertGradCalc2ndOrder), intent(inout) :: this - ! 3 points, 2 elevation classes - real(r8), parameter :: topo(3,2) = reshape( & - [1._r8, 2._r8, 3._r8, & - 4._r8, 5._r8, 6._r8], & - [3, 2]) - real(r8), parameter :: data(3,2) = reshape( & - [11._r8, 12._r8, 13._r8, & - 14._r8, 15._r8, 16._r8], & - [3, 2]) - - call this%create_av(topo, data, 'topo', 'data') - - @assertEqual([4._r8, 5._r8, 6._r8], aVect_exportRattr(this%av, 'topo' // two_digit_string(2))) - - @assertEqual([14._r8, 15._r8, 16._r8], aVect_exportRattr(this%av, 'data' // two_digit_string(2))) - - end subroutine test_create_av - @Test subroutine ECmid(this) ! Test calc_vertical_gradient with an elevation class in the middle of the range diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_test_utils.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_test_utils.pf new file mode 100644 index 00000000000..6d9b87081e2 --- /dev/null +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_test_utils.pf @@ -0,0 +1,61 @@ +module test_vertical_gradient_test_utils + + ! Tests of vertical_gradient_test_utils + + use pfunit_mod + use vertical_gradient_test_utils + use shr_kind_mod , only : r8 => shr_kind_r8 + use mct_mod, only : mct_aVect, mct_aVect_clean + use mct_wrapper_mod, only : mct_init, mct_clean + use avect_wrapper_mod + + implicit none + + @TestCase + type, extends(TestCase) :: TestVertGradTestUtils + type(mct_aVect) :: av + contains + procedure :: setUp + procedure :: tearDown + end type TestVertGradTestUtils + + real(r8), parameter :: tol = 1.e-13_r8 + +contains + + subroutine setUp(this) + class(TestVertGradTestUtils), intent(inout) :: this + + call mct_init() + + end subroutine setUp + + subroutine tearDown(this) + class(TestVertGradTestUtils), intent(inout) :: this + call mct_aVect_clean(this%av) + call mct_clean() + end subroutine tearDown + + @Test + subroutine test_create_av(this) + ! Tests the create_av helper routine + class(TestVertGradTestUtils), intent(inout) :: this + ! 3 points, 2 elevation classes + real(r8), parameter :: topo(3,2) = reshape( & + [1._r8, 2._r8, 3._r8, & + 4._r8, 5._r8, 6._r8], & + [3, 2]) + real(r8), parameter :: data(3,2) = reshape( & + [11._r8, 12._r8, 13._r8, & + 14._r8, 15._r8, 16._r8], & + [3, 2]) + + call create_av(topo, data, 'topo', 'data', this%av) + + @assertEqual([4._r8, 5._r8, 6._r8], aVect_exportRattr(this%av, 'topo' // two_digit_string(2))) + + @assertEqual([14._r8, 15._r8, 16._r8], aVect_exportRattr(this%av, 'data' // two_digit_string(2))) + + end subroutine test_create_av + +end module test_vertical_gradient_test_utils diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 new file mode 100644 index 00000000000..a5b43a79f97 --- /dev/null +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 @@ -0,0 +1,60 @@ +module vertical_gradient_test_utils + + ! Utilities to aid testing of vertical gradient calculators + +#include "shr_assert.h" + use shr_log_mod, only : errMsg => shr_log_errMsg + use shr_kind_mod , only : r8 => shr_kind_r8 + use avect_wrapper_mod + use mct_mod, only : mct_aVect + + implicit none + private + + public :: two_digit_string + public :: create_av + +contains + + function two_digit_string(val) + ! Converts val to a two-digit string + character(len=2) :: two_digit_string + integer, intent(in) :: val + + write(two_digit_string, '(i2.2)') val + end function two_digit_string + + subroutine create_av(topo, data, toponame, dataname, av) + ! Creates the attribute vector 'av' + real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j + real(r8), intent(in) :: data(:,:) ! data(i,j) is point i, elevation class j + character(len=*), intent(in) :: toponame + character(len=*), intent(in) :: dataname + type(mct_aVect), intent(out) :: av + + integer :: npts + integer :: n_elev_classes + integer :: elevclass + character(len=64), allocatable :: attr_tags(:) + + npts = size(topo, 1) + n_elev_classes = size(topo, 2) + + SHR_ASSERT_ALL((ubound(data) == (/npts, n_elev_classes/)), errMsg(__FILE__, __LINE__)) + + allocate(attr_tags(2*n_elev_classes)) + do elevclass = 1, n_elev_classes + attr_tags(elevclass) = dataname // two_digit_string(elevclass) + end do + do elevclass = 1, n_elev_classes + attr_tags(n_elev_classes + elevclass) = toponame // two_digit_string(elevclass) + end do + + call create_aVect_with_data_rows_are_points(av, & + attr_tags = attr_tags, & + data = reshape([data, topo], [npts, n_elev_classes * 2])) + + end subroutine create_av + +end module vertical_gradient_test_utils + From 281bb95628646649fb27a61eec81726737971862 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 27 Apr 2016 14:54:22 -0600 Subject: [PATCH 24/61] Add a basic smoke test of the new calculator Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...ertical_gradient_calculator_continuous.F90 | 3 +- ..._vertical_gradient_calculator_2nd_order.pf | 9 +-- ...vertical_gradient_calculator_continuous.pf | 77 +++++++++++++++++++ .../vertical_gradient_test_utils.F90 | 32 ++++++++ 4 files changed, 112 insertions(+), 9 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 index 37abb07c72c..daca9e98cdc 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -15,7 +15,8 @@ module vertical_gradient_calculator_continuous use mct_mod use shr_log_mod, only : errMsg => shr_log_errMsg use shr_sys_mod, only : shr_sys_abort - + use shr_matrix_mod, only : tridiagonal_inverse + implicit none private diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index 6b63c12f8ab..e7238c84379 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -8,7 +8,6 @@ module test_vertical_gradient_calculator_2nd_order use shr_kind_mod, only : r8 => shr_kind_r8 use mct_mod, only : mct_aVect, mct_aVect_clean use mct_wrapper_mod, only : mct_init, mct_clean - use avect_wrapper_mod use vertical_gradient_test_utils implicit none @@ -54,15 +53,9 @@ contains real(r8), intent(in) :: elevclass_bounds(:) integer :: n_elev_classes - character(len=16), allocatable :: elevclass_names(:) - integer :: i n_elev_classes = size(data,2) @assertEqual(n_elev_classes + 1, size(elevclass_bounds)) - allocate(elevclass_names(1:n_elev_classes)) - do i = 1, n_elev_classes - elevclass_names(i) = two_digit_string(i) - end do call create_av(topo, data, 'topo', 'data', this%av) @@ -72,7 +65,7 @@ contains toponame = 'topo', & min_elevation_class = 1, & max_elevation_class = n_elev_classes, & - elevclass_names = elevclass_names, & + elevclass_names = elevclass_names(n_elev_classes), & elevclass_bounds = elevclass_bounds) end function create_calculator diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf index a636255c38c..8880b7e71df 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -5,14 +5,20 @@ module test_vertical_gradient_calculator_continuous use pfunit_mod use vertical_gradient_calculator_continuous use shr_kind_mod , only : r8 => shr_kind_r8 + use mct_mod, only : mct_aVect, mct_aVect_clean + use mct_wrapper_mod, only : mct_init, mct_clean + use vertical_gradient_test_utils implicit none @TestCase type, extends(TestCase) :: TestVertGradCalcCont + type(mct_aVect) :: av contains procedure :: setUp procedure :: tearDown + procedure :: create_calculator + procedure :: create_calculator_one_point end type TestVertGradCalcCont real(r8), parameter :: tol = 1.e-13_r8 @@ -21,10 +27,81 @@ contains subroutine setUp(this) class(TestVertGradCalcCont), intent(inout) :: this + call mct_init() end subroutine setUp subroutine tearDown(this) class(TestVertGradCalcCont), intent(inout) :: this + call mct_aVect_clean(this%av) + call mct_clean() end subroutine tearDown + function create_calculator(this, topo, data, elevclass_bounds) & + result(calculator) + type(vertical_gradient_calculator_continuous_type) :: calculator + class(TestVertGradCalcCont), intent(inout) :: this + real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j + real(r8), intent(in) :: data(:,:) ! data(i,j) is point i, elevation class j + + ! bounds of each elevation class; this array should have one more element than the + ! number of elevation classes, since it contains lower and upper bounds for each + ! elevation class + real(r8), intent(in) :: elevclass_bounds(:) + + integer :: n_elev_classes + + n_elev_classes = size(data,2) + @assertEqual(n_elev_classes + 1, size(elevclass_bounds)) + + call create_av(topo, data, 'topo', 'data', this%av) + + calculator = vertical_gradient_calculator_continuous_type( & + attr_vect = this%av, & + fieldname = 'data', & + toponame = 'topo', & + nelev = n_elev_classes, & + elevclass_names = elevclass_names(n_elev_classes), & + elevclass_bounds = elevclass_bounds) + + end function create_calculator + + function create_calculator_one_point(this, topo, data, elevclass_bounds) & + result(calculator) + ! Convenience wrapper to create_calculator, when just dealing with one point + type(vertical_gradient_calculator_continuous_type) :: calculator + class(TestVertGradCalcCont), intent(inout) :: this + real(r8), intent(in) :: topo(:) + real(r8), intent(in) :: data(:) + + ! bounds of each elevation class; this array should have one more element than the + ! number of elevation classes, since it contains lower and upper bounds for each + ! elevation class + real(r8), intent(in) :: elevclass_bounds(:) + + + calculator = this%create_calculator( & + topo = reshape(topo, [1, size(topo)]), & + data = reshape(data, [1, size(data)]), & + elevclass_bounds = elevclass_bounds) + end function create_calculator_one_point + + @Test + subroutine basic(this) + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + real(r8), parameter :: elevclass_bounds(6) = & + [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8] + real(r8), parameter :: topo(5) = [10._r8, 30._r8, 50._r8, 70._r8, 90._r8] + real(r8), parameter :: data(5) = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8] + real(r8) :: gradients(5) + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + gradients = all_gradients_one_point(calculator, & + n_elev_classes = 5, & + npts = 1, & + pt = 1) + end subroutine basic + end module test_vertical_gradient_calculator_continuous diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 index a5b43a79f97..7c711499c28 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 @@ -7,12 +7,15 @@ module vertical_gradient_test_utils use shr_kind_mod , only : r8 => shr_kind_r8 use avect_wrapper_mod use mct_mod, only : mct_aVect + use vertical_gradient_calculator_base, only : vertical_gradient_calculator_base_type implicit none private public :: two_digit_string + public :: elevclass_names public :: create_av + public :: all_gradients_one_point ! Return gradients for all ECs for one point contains @@ -24,6 +27,18 @@ function two_digit_string(val) write(two_digit_string, '(i2.2)') val end function two_digit_string + function elevclass_names(n_elev_classes) + ! Returns array of elevation class names + integer, intent(in) :: n_elev_classes + character(len=16) :: elevclass_names(n_elev_classes) + + integer :: i + + do i = 1, n_elev_classes + elevclass_names(i) = two_digit_string(i) + end do + end function elevclass_names + subroutine create_av(topo, data, toponame, dataname, av) ! Creates the attribute vector 'av' real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j @@ -56,5 +71,22 @@ subroutine create_av(topo, data, toponame, dataname, av) end subroutine create_av + function all_gradients_one_point(calculator, n_elev_classes, npts, pt) result(gradients) + ! Return gradients for all ECs for one point + class(vertical_gradient_calculator_base_type), intent(in) :: calculator + integer, intent(in) :: n_elev_classes ! number of elevation classes in this calculator + integer, intent(in) :: npts ! number of points in this calculator + integer, intent(in) :: pt ! point of interest + real(r8) :: gradients(n_elev_classes) ! function result + + integer :: ec + real(r8) :: gradients_one_ec(npts) + + do ec = 1, n_elev_classes + call calculator%calc_vertical_gradient(ec, gradients_one_ec) + gradients(ec) = gradients_one_ec(pt) + end do + end function all_gradients_one_point + end module vertical_gradient_test_utils From bcc51679baf72652add6350ba506c1157eed9c53 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 27 Apr 2016 15:19:02 -0600 Subject: [PATCH 25/61] Remove unused variables Test suite: None Test baseline: N/A Test namelist changes: N/A Test status: N/A Fixes: None User interface changes?: No Code review: None --- .../driver/vertical_gradient_calculator_continuous.F90 | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 index daca9e98cdc..8e3ffab99f6 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -26,8 +26,6 @@ module vertical_gradient_calculator_continuous vertical_gradient_calculator_continuous_type private - integer :: min_elevation_class - integer :: max_elevation_class integer :: nelev ! number of elevation classes integer :: num_points real(r8), allocatable :: field(:,:) ! field(i,j) is elevation class i, field j @@ -37,8 +35,8 @@ module vertical_gradient_calculator_continuous ! Bounds of each elevation class. This array has one more element than the number of ! elevation classes, since it contains lower and upper bounds for each elevation - ! class. The indices go (min_elevation_class-1):max_elevation_class. These bounds - ! are guaranteed to be monotonically increasing. + ! class. The indices go 0:nelev. These bounds are guaranteed to be monotonically + ! increasing. real(r8), allocatable :: elevclass_bounds(:) contains From f647995963d438900ee17032deb7fa2662976026 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 27 Apr 2016 15:36:18 -0600 Subject: [PATCH 26/61] Output gradients to file Also fix code so unit tests pass Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...ertical_gradient_calculator_continuous.F90 | 9 ++-- ...vertical_gradient_calculator_continuous.pf | 42 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 index 8e3ffab99f6..5b595d6f162 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -281,8 +281,8 @@ subroutine precompute_vertical_gradients(this, pt) du(i) = topo_interface(i) - topo(i) ! du(n) is never used end do - ! FIXME(wjs, 2016-04-26) Extract method for this loop: returns weight_grad in each - ! elevation class + ! FIXME(wjs, 2016-04-26) Extract method for the following two loops: returns + ! weight_grad in each elevation class do i = 1, n if (i == 1) then @@ -301,6 +301,9 @@ subroutine precompute_vertical_gradients(this, pt) h_hi(i) = topo_interface(i) end if + end do + + do i = 1, n ! set gradient weights based on h_hi - h_lo in each class weight_grad(i) = (h_hi(i) - h_lo(i)) / (h_hi(n) - h_lo(1)) @@ -376,7 +379,7 @@ subroutine precompute_vertical_gradients(this, pt) end do ! Compute A * A^T, a tridiagonal matrix of size (n-1) - + A_AT = matmul(A, transpose(A)) ! Compute tridiagonal entries of (A * A^T) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf index 8880b7e71df..232bd9c818c 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -10,6 +10,7 @@ module test_vertical_gradient_calculator_continuous use vertical_gradient_test_utils implicit none + save @TestCase type, extends(TestCase) :: TestVertGradCalcCont @@ -19,21 +20,29 @@ module test_vertical_gradient_calculator_continuous procedure :: tearDown procedure :: create_calculator procedure :: create_calculator_one_point + procedure :: write_output end type TestVertGradCalcCont real(r8), parameter :: tol = 1.e-13_r8 + integer, parameter :: out_unit = 11 + integer :: test_num = 0 contains subroutine setUp(this) class(TestVertGradCalcCont), intent(inout) :: this + character(len=32) :: filename call mct_init() + test_num = test_num + 1 + write(filename, '(a, i0, a)') 'gradients_continuous_', test_num, '.txt' + open(out_unit, file=filename, action='write') end subroutine setUp subroutine tearDown(this) class(TestVertGradCalcCont), intent(inout) :: this call mct_aVect_clean(this%av) call mct_clean() + close(out_unit) end subroutine tearDown function create_calculator(this, topo, data, elevclass_bounds) & @@ -85,6 +94,32 @@ contains elevclass_bounds = elevclass_bounds) end function create_calculator_one_point + subroutine write_output(this, elevclass_bounds, topo, data, gradients) + class(TestVertGradCalcCont), intent(inout) :: this + real(r8), intent(in) :: elevclass_bounds(:) + real(r8), intent(in) :: topo(:) + real(r8), intent(in) :: data(:) + real(r8), intent(in) :: gradients(:) + + integer :: n_elev_classes + character(len=32) :: bounds_format + character(len=32) :: data_format + + n_elev_classes = size(gradients) + @assertEqual(n_elev_classes + 1, size(elevclass_bounds)) + @assertEqual(n_elev_classes, size(topo)) + @assertEqual(n_elev_classes, size(data)) + + write(bounds_format, '(a, i0, a, a)') '(', n_elev_classes + 1, 'f16.10', ')' + write(data_format, '(a, i0, a, a)') '(', n_elev_classes, 'f16.10', ')' + + write(out_unit, '(i0)') n_elev_classes + write(out_unit, bounds_format) elevclass_bounds + write(out_unit, data_format) topo + write(out_unit, data_format) data + write(out_unit, data_format) gradients + end subroutine write_output + @Test subroutine basic(this) class(TestVertGradCalcCont), intent(inout) :: this @@ -102,6 +137,13 @@ contains n_elev_classes = 5, & npts = 1, & pt = 1) + + call this%write_output( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + gradients = gradients) + end subroutine basic end module test_vertical_gradient_calculator_continuous From 7979df551cc48dc84681b3b2ef7a23754bfbb946 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 27 Apr 2016 15:40:22 -0600 Subject: [PATCH 27/61] Switch production code to use new gradient calculator Test suite: None Test baseline: N/A Test namelist changes: N/A Test status: N/A Fixes: None User interface changes?: No Code review: None --- driver_cpl/driver/prep_glc_mod.F90 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/driver_cpl/driver/prep_glc_mod.F90 b/driver_cpl/driver/prep_glc_mod.F90 index d0a3eae5487..7e9d425e9aa 100644 --- a/driver_cpl/driver/prep_glc_mod.F90 +++ b/driver_cpl/driver/prep_glc_mod.F90 @@ -406,7 +406,7 @@ subroutine prep_glc_map_one_field_lnd2glc(egi, eli, fieldname, fractions_lx, map ! Note that we remap each field separately because each field needs its own ! vertical gradient calculator. - use vertical_gradient_calculator_2nd_order, only : vertical_gradient_calculator_2nd_order_type + use vertical_gradient_calculator_continuous, only : vertical_gradient_calculator_continuous_type use glc_elevclass_mod, only : glc_get_num_elevation_classes, & glc_get_elevclass_bounds, glc_all_elevclass_strings use map_lnd2glc_mod, only : map_lnd2glc @@ -420,17 +420,16 @@ subroutine prep_glc_map_one_field_lnd2glc(egi, eli, fieldname, fractions_lx, map ! ! Local Variables type(mct_avect), pointer :: g2x_gx - type(vertical_gradient_calculator_2nd_order_type) :: gradient_calculator + type(vertical_gradient_calculator_continuous_type) :: gradient_calculator !--------------------------------------------------------------- g2x_gx => component_get_c2x_cx(glc(egi)) - gradient_calculator = vertical_gradient_calculator_2nd_order_type( & + gradient_calculator = vertical_gradient_calculator_continuous_type( & attr_vect = l2gacc_lx(eli), & fieldname = fieldname, & toponame = 'Sl_topo', & - min_elevation_class = 1, & - max_elevation_class = glc_get_num_elevation_classes(), & + nelev = glc_get_num_elevation_classes(), & elevclass_names = glc_all_elevclass_strings(), & elevclass_bounds = glc_get_elevclass_bounds()) call map_lnd2glc(l2x_l = l2gacc_lx(eli), & From 5a0c5615110258eed74f175520338decd11f8592 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 27 Apr 2016 16:50:55 -0600 Subject: [PATCH 28/61] Add assertion for expected gradients Eventually I should separate the assertion from the output writing, into separate pf files. Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- .../test_vertical_gradient_calculator_continuous.pf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf index 232bd9c818c..d73a6ca894a 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -129,6 +129,7 @@ contains real(r8), parameter :: topo(5) = [10._r8, 30._r8, 50._r8, 70._r8, 90._r8] real(r8), parameter :: data(5) = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8] real(r8) :: gradients(5) + real(r8) :: expected_gradients(5) calculator = this%create_calculator_one_point(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) @@ -138,6 +139,9 @@ contains npts = 1, & pt = 1) + expected_gradients(:) = [0.2_r8, 0.15_r8, 0.10_r8, 0.05_r8, 0.0_r8] + @assertEqual(expected_gradients, gradients, tolerance=tol) + call this%write_output( & elevclass_bounds = elevclass_bounds, & topo = topo, & From 351fb920129f881b3de1e86f877136ce2a2c134d Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 27 Apr 2016 17:19:28 -0600 Subject: [PATCH 29/61] Handle topo values out of bounds To handle the fact that some TG data are messed up: don't abort if the check fails, but set all gradients to NaN in that case Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...ertical_gradient_calculator_continuous.F90 | 100 ++++++++++++++++-- ...vertical_gradient_calculator_continuous.pf | 46 ++++++++ 2 files changed, 136 insertions(+), 10 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 index 5b595d6f162..3f05ad78bcc 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -28,11 +28,13 @@ module vertical_gradient_calculator_continuous integer :: nelev ! number of elevation classes integer :: num_points - real(r8), allocatable :: field(:,:) ! field(i,j) is elevation class i, field j - real(r8), allocatable :: topo(:,:) ! topo(i,j) is elevation class i, field j + real(r8), allocatable :: field(:,:) ! field(i,j) is elevation class i, point j + real(r8), allocatable :: topo(:,:) ! topo(i,j) is elevation class i, point j real(r8), allocatable :: vertical_gradient(:,:) ! precomputed vertical gradients; vertical_gradient(i,j) is elevation class i, field j + logical, allocatable :: topo_valid(:) ! whether topo is valid in each point + ! Bounds of each elevation class. This array has one more element than the number of ! elevation classes, since it contains lower and upper bounds for each elevation ! class. The indices go 0:nelev. These bounds are guaranteed to be monotonically @@ -42,8 +44,10 @@ module vertical_gradient_calculator_continuous contains procedure :: calc_vertical_gradient + procedure, private :: check_topo ! check topographic heights procedure, private :: set_data_from_attr_vect ! extract data from an attribute vector procedure, private :: precompute_vertical_gradients ! compute vertical gradients for all ECs + procedure, private :: solve_for_vertical_gradients ! compute vertical gradients for all ECs, for points where we do a matrix solve end type vertical_gradient_calculator_continuous_type @@ -109,14 +113,12 @@ function constructor(attr_vect, fieldname, toponame, & call this%set_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names) + allocate(this%topo_valid(this%num_points)) + call this%check_topo() + allocate(this%vertical_gradient(this%nelev, this%num_points)) this%vertical_gradient(:,:) = nan - ! FIXME(wjs, 2016-04-26) Uncomment this call to check_topo - but change it so that it - ! sets a flag, and then we'll set vertical gradients to 0 wherever topos are bad. - ! - ! call this%check_topo() - ! For this implementation of the vertical gradient calculator, we compute all vertical ! gradients in object construction. This is because we compute them all simultaneously ! rather than independently. (So then, the call to the routine that would normally @@ -218,11 +220,89 @@ subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame, elevcla end subroutine set_data_from_attr_vect + !----------------------------------------------------------------------- + subroutine check_topo(this) + ! + ! !DESCRIPTION: + ! Check topographic heights; set this%topo_valid(i) to false if there is a problem in + ! point i. + ! + ! Topographic heights in the attribute vector must all lie inside the bounds of their + ! respective elevation class (given by elevclass_bounds), with the possible exception + ! of the lowest elevation class (topographic heights can lie below the arbitrary lower + ! bound of the elevation class) and the highest elevation class (topographic heights + ! can lie above the arbitrary upper bound of the elevation class) + ! + ! !USES: + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_continuous_type), intent(inout) :: this + ! + ! !LOCAL VARIABLES: + integer :: elevclass + integer :: i + + ! Absolute tolerance for error checks. This is chosen so that it allows for + ! double-precision roundoff-level errors on values of order 10,000. + real(r8), parameter :: tol = 1.e-10_r8 + + character(len=*), parameter :: subname = 'check_topo' + !----------------------------------------------------------------------- + + this%topo_valid(:) = .true. + + do i = 1, this%num_points + do elevclass = 1, this%nelev + if (elevclass > 1) then + if (this%topo(elevclass,i) - this%elevclass_bounds(elevclass-1) < -tol) then + this%topo_valid(i) = .false. + end if + end if + + if (elevclass < this%nelev) then + if (this%topo(elevclass,i) - this%elevclass_bounds(elevclass) > tol) then + this%topo_valid(i) = .false. + end if + end if + end do + end do + + end subroutine check_topo + !----------------------------------------------------------------------- subroutine precompute_vertical_gradients(this, pt) ! ! !DESCRIPTION: - ! Compute and save vertical gradients for all elevation classes. + ! Compute and save vertical gradients for all elevation classes in this point. + ! + ! !USES: + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_continuous_type), intent(inout) :: this + integer, intent(in) :: pt ! point to compute gradients for (1..this%num_points) + ! + ! !LOCAL VARIABLES: + + character(len=*), parameter :: subname = 'precompute_vertical_gradients' + !----------------------------------------------------------------------- + + if (.not. this%topo_valid(pt)) then + this%vertical_gradient(:,pt) = nan + else + call this%solve_for_vertical_gradients(pt) + end if + + end subroutine precompute_vertical_gradients + + + !----------------------------------------------------------------------- + subroutine solve_for_vertical_gradients(this, pt) + ! + ! !DESCRIPTION: + ! Compute and save vertical gradients for all elevation classes in this point. + ! + ! This should only be called for points where we have done some initial checks to + ! show that we should attempt a matrix solve there. ! ! Computes a gradient in each elevation class such that the field is continuous at ! interfaces and the sum over squared differences from the mean is minimized. @@ -264,7 +344,7 @@ subroutine precompute_vertical_gradients(this, pt) integer :: i - character(len=*), parameter :: subname = 'precompute_vertical_gradients' + character(len=*), parameter :: subname = 'solve_for_vertical_gradients' !----------------------------------------------------------------------- field(:) = this%field(:,pt) @@ -433,7 +513,7 @@ subroutine precompute_vertical_gradients(this, pt) this%vertical_gradient(:,pt) = grad(:) - end subroutine precompute_vertical_gradients + end subroutine solve_for_vertical_gradients end module vertical_gradient_calculator_continuous diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf index d73a6ca894a..1261165275c 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -150,4 +150,50 @@ contains end subroutine basic + @Test + subroutine topo_outOfBoundsHigh(this) + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = & + [0._r8, 20._r8, 40._r8, 60._r8] + real(r8), parameter :: topo(3) = [10._r8, 40._r8 + 1.e-5_r8, 50._r8] + real(r8), parameter :: data(3) = [2._r8, 5.5_r8, 8._r8] + real(r8) :: gradients(3) + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + gradients = all_gradients_one_point(calculator, & + n_elev_classes = 3, & + npts = 1, & + pt = 1) + + @assertIsNan(gradients(1)) + @assertIsNan(gradients(2)) + @assertIsNan(gradients(3)) + end subroutine topo_outOfBoundsHigh + + @Test + subroutine topo_outOfBoundsLow(this) + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = & + [0._r8, 20._r8, 40._r8, 60._r8] + real(r8), parameter :: topo(3) = [10._r8, 20._r8 - 1.e-5_r8, 50._r8] + real(r8), parameter :: data(3) = [2._r8, 5.5_r8, 8._r8] + real(r8) :: gradients(3) + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + gradients = all_gradients_one_point(calculator, & + n_elev_classes = 3, & + npts = 1, & + pt = 1) + + @assertIsNan(gradients(1)) + @assertIsNan(gradients(2)) + @assertIsNan(gradients(3)) + end subroutine topo_outOfBoundsLow + end module test_vertical_gradient_calculator_continuous From 3601e782e58e214e902db43dcde844a3d576c61e Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 27 Apr 2016 17:45:37 -0600 Subject: [PATCH 30/61] For problematic topo values, set gradient to 0 rather than NaN It turns out that setting gradients to NaN causes floating point exceptions in map_lnd2glc, in this line: partial_remap_l = data_l - (vertical_gradient_l * topo_l) Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- .../driver/vertical_gradient_calculator_continuous.F90 | 2 +- .../test_vertical_gradient_calculator_continuous.pf | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 index 3f05ad78bcc..c4a01bb8fc5 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -287,7 +287,7 @@ subroutine precompute_vertical_gradients(this, pt) !----------------------------------------------------------------------- if (.not. this%topo_valid(pt)) then - this%vertical_gradient(:,pt) = nan + this%vertical_gradient(:,pt) = 0._r8 else call this%solve_for_vertical_gradients(pt) end if diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf index 1261165275c..db08e698d73 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -168,9 +168,7 @@ contains npts = 1, & pt = 1) - @assertIsNan(gradients(1)) - @assertIsNan(gradients(2)) - @assertIsNan(gradients(3)) + @assertEqual([0._r8, 0._r8, 0._r8], gradients) end subroutine topo_outOfBoundsHigh @Test @@ -191,9 +189,7 @@ contains npts = 1, & pt = 1) - @assertIsNan(gradients(1)) - @assertIsNan(gradients(2)) - @assertIsNan(gradients(3)) + @assertEqual([0._r8, 0._r8, 0._r8], gradients) end subroutine topo_outOfBoundsLow end module test_vertical_gradient_calculator_continuous From 2be8d788330bd03160657d4844cd61b09662db13 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Thu, 28 Apr 2016 14:48:28 -0600 Subject: [PATCH 31/61] Add more tests for plotting, and tweak plotter In plotter: let axes be chosen automatically Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- .../plot_gradient | 13 +- ...vertical_gradient_calculator_continuous.pf | 165 +++++++++++++++++- 2 files changed, 168 insertions(+), 10 deletions(-) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient b/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient index 1a758153b82..b63c505a332 100755 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient @@ -84,21 +84,18 @@ class GradientInfo: field_min = min(self.field) field_max = max(self.field) - y_range = field_max - field_min - y_max = field_max + 0.2 * y_range - y_min = field_min - 0.2 * y_range - - plt.axis([elev_min, elev_max, y_min, y_max]) plt.plot(self.topo, self.field, 'ro') - for ec_bound in self.elevclass_bounds: - plt.plot([ec_bound, ec_bound], [y_min, y_max], 'k') - for ec in range(self.nelev): (xs, ys) = gradient_line(self.topo[ec], self.field[ec], self.gradient[ec], self.elevclass_bounds[ec], self.elevclass_bounds[ec+1]) plt.plot(xs, ys, 'b') + # plot elevation class bounds - vertical lines + ylim = plt.axes().get_ylim() + for ec_bound in self.elevclass_bounds: + plt.plot([ec_bound, ec_bound], ylim, 'k') + pylab.savefig(output_filename) plt.close() diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf index db08e698d73..94a34211431 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -110,8 +110,8 @@ contains @assertEqual(n_elev_classes, size(topo)) @assertEqual(n_elev_classes, size(data)) - write(bounds_format, '(a, i0, a, a)') '(', n_elev_classes + 1, 'f16.10', ')' - write(data_format, '(a, i0, a, a)') '(', n_elev_classes, 'f16.10', ')' + write(bounds_format, '(a, i0, a, a)') '(', n_elev_classes + 1, 'f20.10', ')' + write(data_format, '(a, i0, a, a)') '(', n_elev_classes, 'f20.10', ')' write(out_unit, '(i0)') n_elev_classes write(out_unit, bounds_format) elevclass_bounds @@ -142,6 +142,7 @@ contains expected_gradients(:) = [0.2_r8, 0.15_r8, 0.10_r8, 0.05_r8, 0.0_r8] @assertEqual(expected_gradients, gradients, tolerance=tol) + ! FIXME(wjs, 2016-04-27) Move this to somewhere else call this%write_output( & elevclass_bounds = elevclass_bounds, & topo = topo, & @@ -150,6 +151,166 @@ contains end subroutine basic + ! FIXME(wjs, 2016-04-27) delete this + @Test + subroutine real_temp(this) + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + ! Using a max of 4000 in the top elevclass should give the same results as 10000, and + ! will make for a prettier figure + real(r8), parameter :: elevclass_bounds(11) = & + [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 1300._r8, & + 1600._r8, 2000._r8, 2500._r8, 3000._r8, 4000._r8] + real(r8) :: topo(10) + real(r8) :: data(10) + real(r8) :: gradients(10) + + topo(1) = 150.d0 + topo(2) = 370.d0 + topo(3) = 618.d0 + topo(4) = 777.d0 + topo(5) = 1205.d0 + topo(6) = 1372.d0 + topo(7) = 1800.d0 + topo(8) = 2250.d0 + topo(9) = 2750.d0 + topo(10)= 3500.d0 + + data(1) = -3.89d0 + data(2) = -2.42d0 + data(3) = -0.71d0 + data(4) = 0.00d0 + data(5) = 2.19d0 + data(6) = 2.19d0 + data(7) = 2.19d0 + data(8) = 2.19d0 + data(9) = 2.20d0 + data(10)= 2.74d0 + + + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + gradients = all_gradients_one_point(calculator, & + n_elev_classes = 10, & + npts = 1, & + pt = 1) + + call this%write_output( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + gradients = gradients) + end subroutine real_temp + + ! FIXME(wjs, 2016-04-27) move this elsewhere + @Test + subroutine real_data1(this) + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + ! Using a max of 4000 in the top elevclass should give the same results as 10000, and + ! will make for a prettier figure + real(r8), parameter :: elevclass_bounds(11) = & + [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 1300._r8, & + 1600._r8, 2000._r8, 2500._r8, 3000._r8, 4000._r8] + real(r8), parameter :: topo(10) = & + [150.45797729492188_r8, 369.68896484375_r8, 618.4522705078125_r8, 776.9857177734375_r8, & + 1205.492919921875_r8, 1372.2435302734375_r8, 1800.0_r8, 2250.0_r8, 2750.0_r8, 3500.0_r8] + real(r8), parameter :: data(10) = & + [-3.8940095691941679e-05_r8, -2.4159431632142514e-05_r8, -7.1326958277495578e-06_r8, & + 3.2833636254281373e-08_r8, 2.1934458345640451e-05_r8, 2.1910125724389218e-05_r8, & + 2.191431303799618e-05_r8, 2.1943444153293967e-05_r8, 2.1968355213175528e-05_r8, & + 2.7414380383561365e-05_r8] + real(r8) :: gradients(10) + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + gradients = all_gradients_one_point(calculator, & + n_elev_classes = 10, & + npts = 1, & + pt = 1) + + call this%write_output( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + gradients = gradients) + end subroutine real_data1 + + ! FIXME(wjs, 2016-04-27) move this elsewhere + @Test + subroutine real_data1_combinedA(this) + ! See what happens when we combine the data from real_data1 that all have + ! approximately equal SMB + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + ! Using a max of 4000 in the top elevclass should give the same results as 10000, and + ! will make for a prettier figure + real(r8), parameter :: elevclass_bounds(7) = & + [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 3000._r8, 4000._r8] + real(r8), parameter :: topo(6) = & + [150.45797729492188_r8, 369.68896484375_r8, 618.4522705078125_r8, 776.9857177734375_r8, & + 2146.2894083658853_r8, & ! mean of elevation classes 5-9 + 3500.0_r8] + real(r8), parameter :: data(6) = & + [-3.8940095691941679e-05_r8, -2.4159431632142514e-05_r8, -7.1326958277495578e-06_r8, & + 3.2833636254281373e-08_r8, & + 2.1934139294899068e-05_r8, & ! mean of elevation classes 5-9 + 2.7414380383561365e-05_r8] + real(r8) :: gradients(6) + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + gradients = all_gradients_one_point(calculator, & + n_elev_classes = 6, & + npts = 1, & + pt = 1) + + call this%write_output( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + gradients = gradients) + end subroutine real_data1_combinedA + + ! FIXME(wjs, 2016-04-27) move this elsewhere + @Test + subroutine real_data2(this) + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + ! Using a max of 4000 in the top elevclass should give the same results as 10000, and + ! will make for a prettier figure + real(r8), parameter :: elevclass_bounds(11) = & + [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 1300._r8, & + 1600._r8, 2000._r8, 2500._r8, 3000._r8, 4000._r8] + real(r8), parameter :: topo(10) = & + [100.0_r8, 300.0_r8, 553.73822021484375_r8, 843.978759765625_r8, 1152.2908935546875_r8, & + 1450.9669189453125_r8, 1628.5628662109375_r8, 2250.0_r8, 2750.0_r8, 3500.0_r8] + real(r8), parameter :: data(10) = & + [-3.2589337934041396e-05_r8, -6.7787163970933761e-06_r8, 0.0_r8, & + 8.7906073531485163e-06_r8, 8.6524905782425776e-06_r8, 8.2202923294971697e-06_r8, & + 7.9119627116597258e-06_r8, 7.5692469181376509e-06_r8, 7.3112623795168474e-06_r8, & + 7.0084388426039368e-06_r8] + real(r8) :: gradients(10) + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + gradients = all_gradients_one_point(calculator, & + n_elev_classes = 10, & + npts = 1, & + pt = 1) + + call this%write_output( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + gradients = gradients) + end subroutine real_data2 + @Test subroutine topo_outOfBoundsHigh(this) class(TestVertGradCalcCont), intent(inout) :: this From 6d901d843d541e39bfd00a37d9962b3faae6d43b Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 29 Apr 2016 05:56:38 -0600 Subject: [PATCH 32/61] Add place-holder for limiting gradients Test suite: None Test baseline: N/A Test namelist changes: N/A Test status: N/A Fixes: None User interface changes?: No Code review: None --- .../driver/vertical_gradient_calculator_continuous.F90 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 index c4a01bb8fc5..b1e17f88776 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -511,6 +511,12 @@ subroutine solve_for_vertical_gradients(this, pt) ! Add dgrad to the mean to get the total gradient grad(:) = grad_mean + dgrad(:) + ! Limit gradients + + ! FIXME(wjs, 2016-04-28) Extract this into a subroutine, or into a separate class + + + ! Finally, set class-level values this%vertical_gradient(:,pt) = grad(:) end subroutine solve_for_vertical_gradients From be9126fda647261b3c8756f36a1e471263402ec7 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 29 Apr 2016 08:18:20 -0600 Subject: [PATCH 33/61] Begin work on factory to create gradient calculators Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- driver_cpl/driver/CMakeLists.txt | 1 + .../vertical_gradient_calculator_factory.F90 | 79 +++++++++++++++++++ .../CMakeLists.txt | 1 + ...st_vertical_gradient_calculator_factory.pf | 61 ++++++++++++++ 4 files changed, 142 insertions(+) create mode 100644 driver_cpl/driver/vertical_gradient_calculator_factory.F90 create mode 100644 driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_factory.pf diff --git a/driver_cpl/driver/CMakeLists.txt b/driver_cpl/driver/CMakeLists.txt index 676b749e0b9..4b182fa1f2d 100644 --- a/driver_cpl/driver/CMakeLists.txt +++ b/driver_cpl/driver/CMakeLists.txt @@ -7,6 +7,7 @@ list(APPEND drv_sources vertical_gradient_calculator_base.F90 vertical_gradient_calculator_2nd_order.F90 vertical_gradient_calculator_continuous.F90 + vertical_gradient_calculator_factory.F90 ) sourcelist_to_parent(drv_sources) \ No newline at end of file diff --git a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 new file mode 100644 index 00000000000..431c6c9df6b --- /dev/null +++ b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 @@ -0,0 +1,79 @@ +module vertical_gradient_calculator_factory + !--------------------------------------------------------------------- + ! + ! Purpose: + ! + ! This module creates vertical gradient objects + + use shr_kind_mod, only : r8 => shr_kind_r8 + use mct_mod + + implicit none + private + +! public :: create_vertical_gradient_calculator_2nd_order + + ! The following routines are public just to support unit testing, and shouldn't be + ! called from production code + public :: extract_data_from_attr_vect + +contains + + !----------------------------------------------------------------------- + subroutine extract_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names, & + field_extracted, topo_extracted) + ! + ! !DESCRIPTION: + ! Extract topo and data from attribute vector. + ! + ! Allocates and sets topo_extracted and data_extracted + ! + ! !USES: + ! + ! !ARGUMENTS: + type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data + character(len=*) , intent(in) :: fieldname ! base name of the field of interest + character(len=*) , intent(in) :: toponame ! base name of the topographic field + character(len=*) , intent(in) :: elevclass_names(:) ! strings corresponding to each elevation class + + ! field_extracted(i,j) is point i, elevation class j; same for topo_extracted + ! these are both allocated here + real(r8), intent(out), allocatable :: field_extracted(:,:) + real(r8), intent(out), allocatable :: topo_extracted(:,:) + ! + ! !LOCAL VARIABLES: + integer :: npts + integer :: nelev + integer :: ec + character(len=:), allocatable :: fieldname_ec + character(len=:), allocatable :: toponame_ec + + ! The following temporary array is needed because mct wants pointers + real(r8), pointer :: temp(:) + + character(len=*), parameter :: subname = 'extract_data_from_attr_vect' + !----------------------------------------------------------------------- + + nelev = size(elevclass_names) + npts = mct_aVect_lsize(attr_vect) + + allocate(field_extracted(npts, nelev)) + allocate(topo_extracted(npts, nelev)) + allocate(temp(npts)) + + do ec = 1, nelev + fieldname_ec = trim(fieldname) // trim(elevclass_names(ec)) + call mct_aVect_exportRattr(attr_vect, fieldname_ec, temp) + field_extracted(:,ec) = temp(:) + + toponame_ec = trim(toponame) // trim(elevclass_names(ec)) + call mct_aVect_exportRattr(attr_vect, toponame_ec, temp) + topo_extracted(:,ec) = temp(:) + end do + + deallocate(temp) + + end subroutine extract_data_from_attr_vect + + +end module vertical_gradient_calculator_factory diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt index 09b2369c075..501e5265514 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt @@ -2,6 +2,7 @@ set (pfunit_sources test_vertical_gradient_test_utils.pf test_vertical_gradient_calculator_2nd_order.pf test_vertical_gradient_calculator_continuous.pf + test_vertical_gradient_calculator_factory.pf ) set (extra_sources diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_factory.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_factory.pf new file mode 100644 index 00000000000..638c3ef8981 --- /dev/null +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_factory.pf @@ -0,0 +1,61 @@ +module test_vertical_gradient_calculator_factory + + ! Tests of vertical_gradient_calculator_factory + + use pfunit_mod + use vertical_gradient_calculator_factory + use shr_kind_mod , only : r8 => shr_kind_r8 + use vertical_gradient_test_utils + use mct_mod, only : mct_aVect, mct_aVect_clean + use mct_wrapper_mod, only : mct_init, mct_clean + + implicit none + + @TestCase + type, extends(TestCase) :: TestVertGradCalcFactory + type(mct_aVect) :: av + contains + procedure :: setUp + procedure :: tearDown + end type TestVertGradCalcFactory + + real(r8), parameter :: tol = 1.e-13_r8 + +contains + + subroutine setUp(this) + class(TestVertGradCalcFactory), intent(inout) :: this + + call mct_init() + end subroutine setUp + + subroutine tearDown(this) + class(TestVertGradCalcFactory), intent(inout) :: this + + call mct_aVect_clean(this%av) + call mct_clean() + end subroutine tearDown + + @Test + subroutine test_extract_data(this) + class(TestVertGradCalcFactory), intent(inout) :: this + integer, parameter :: npts = 2 + integer, parameter :: nelev = 3 + real(r8), parameter :: topo(npts,nelev) = & + reshape([1._r8, 2._r8, 3._r8, 4._r8, 5._r8, 6._r8], [npts, nelev]) + real(r8), parameter :: data(npts,nelev) = & + reshape([11._r8, 12._r8, 13._r8, 14._r8, 15._r8, 16._r8], [npts, nelev]) + real(r8), allocatable :: topo_extracted(:,:) + real(r8), allocatable :: data_extracted(:,:) + + call create_av(topo, data, 'topo', 'data', this%av) + + call extract_data_from_attr_vect(this%av, 'data', 'topo', elevclass_names(nelev), & + data_extracted, topo_extracted) + + @assertEqual(data, data_extracted) + @assertEqual(topo, topo_extracted) + end subroutine test_extract_data + +end module test_vertical_gradient_calculator_factory + From 8eb4a3cc95a5adc1c2cbbb763b6618fb1a64f3e8 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 29 Apr 2016 08:29:29 -0600 Subject: [PATCH 34/61] Add start of create_vertical_gradient_calculator_2nd_order Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- .../vertical_gradient_calculator_factory.F90 | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 index 431c6c9df6b..6c6f1d58c33 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 @@ -5,13 +5,16 @@ module vertical_gradient_calculator_factory ! ! This module creates vertical gradient objects +#include "shr_assert.h" use shr_kind_mod, only : r8 => shr_kind_r8 + use shr_log_mod, only : errMsg => shr_log_errMsg + use vertical_gradient_calculator_2nd_order, only : vertical_gradient_calculator_2nd_order_type use mct_mod implicit none private -! public :: create_vertical_gradient_calculator_2nd_order + public :: create_vertical_gradient_calculator_2nd_order ! The following routines are public just to support unit testing, and shouldn't be ! called from production code @@ -19,6 +22,43 @@ module vertical_gradient_calculator_factory contains + !----------------------------------------------------------------------- + function create_vertical_gradient_calculator_2nd_order( & + attr_vect, fieldname, toponame, elevclass_names, elevclass_bounds) & + result(calculator) + ! + ! !DESCRIPTION: + ! Creates and returns a vertical_gradient_calculator_2nd_order_type object + ! + ! !USES: + ! + ! !ARGUMENTS: + type(vertical_gradient_calculator_2nd_order_type) :: calculator ! function result + type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data + character(len=*) , intent(in) :: fieldname ! base name of the field of interest + character(len=*) , intent(in) :: toponame ! base name of the topographic field + character(len=*) , intent(in) :: elevclass_names(:) ! strings corresponding to each elevation class + ! bounds of each elevation class; this array should have one more element than the + ! number of elevation classes, since it contains lower and upper bounds for each + ! elevation class + real(r8) , intent(in) :: elevclass_bounds(0:) + ! + ! !LOCAL VARIABLES: + integer :: nelev + real(r8), allocatable :: field(:,:) + real(r8), allocatable :: topo(:,:) + + character(len=*), parameter :: subname = 'create_vertical_gradient_calculator_2nd_order' + !----------------------------------------------------------------------- + + nelev = size(elevclass_names) + SHR_ASSERT_ALL((ubound(elevclass_bounds) == (/nelev/)), errMsg(__FILE__, __LINE__)) + + call extract_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names, & + field, topo) + + end function create_vertical_gradient_calculator_2nd_order + !----------------------------------------------------------------------- subroutine extract_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names, & field_extracted, topo_extracted) From b82790593679bed1384bc62932c50415936eb5dd Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 29 Apr 2016 08:39:08 -0600 Subject: [PATCH 35/61] Remove unnecessary ability to specify min_elevation_class This simplifies the code Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...vertical_gradient_calculator_2nd_order.F90 | 51 +++++++++---------- ..._vertical_gradient_calculator_2nd_order.pf | 2 - 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index 43e1e57723c..84d92cd0e3b 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -24,15 +24,14 @@ module vertical_gradient_calculator_2nd_order vertical_gradient_calculator_2nd_order_type private - integer :: min_elevation_class - integer :: max_elevation_class + integer :: nelev ! number of elevation classes integer :: num_points real(r8), allocatable :: field(:,:) ! field(i,j) is point i, elevation class j real(r8), allocatable :: topo(:,:) ! topo(i,j) is point i, elevation class j ! Bounds of each elevation class. This array has one more element than the number of ! elevation classes, since it contains lower and upper bounds for each elevation - ! class. The indices go (min_elevation_class-1):max_elevation_class. These bounds + ! class. The indices go 0:nelev. These bounds ! are guaranteed to be monotonically increasing. real(r8), allocatable :: elevclass_bounds(:) @@ -53,8 +52,7 @@ module vertical_gradient_calculator_2nd_order !----------------------------------------------------------------------- function constructor(attr_vect, fieldname, toponame, & - min_elevation_class, max_elevation_class, elevclass_names, & - elevclass_bounds) & + elevclass_names, elevclass_bounds) & result(this) ! ! !DESCRIPTION: @@ -82,28 +80,26 @@ function constructor(attr_vect, fieldname, toponame, & type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data character(len=*) , intent(in) :: fieldname ! base name of the field of interest character(len=*) , intent(in) :: toponame ! base name of the topographic field - integer , intent(in) :: min_elevation_class ! first elevation class index - integer , intent(in) :: max_elevation_class ! last elevation class index ! strings corresponding to each elevation class - character(len=*) , intent(in) :: elevclass_names( min_elevation_class: ) + character(len=*) , intent(in) :: elevclass_names(:) ! bounds of each elevation class; this array should have one more element than the ! number of elevation classes, since it contains lower and upper bounds for each ! elevation class - real(r8) , intent(in) :: elevclass_bounds( min_elevation_class-1 : ) + real(r8) , intent(in) :: elevclass_bounds(0:) ! ! !LOCAL VARIABLES: - + integer :: nelev + character(len=*), parameter :: subname = 'constructor' !----------------------------------------------------------------------- - SHR_ASSERT_ALL((ubound(elevclass_names) == (/max_elevation_class/)), errMsg(__FILE__, __LINE__)) - SHR_ASSERT_ALL((ubound(elevclass_bounds) == (/max_elevation_class/)), errMsg(__FILE__, __LINE__)) + nelev = size(elevclass_names) + SHR_ASSERT_ALL((ubound(elevclass_bounds) == (/nelev/)), errMsg(__FILE__, __LINE__)) - this%min_elevation_class = min_elevation_class - this%max_elevation_class = max_elevation_class - allocate(this%elevclass_bounds((min_elevation_class-1):max_elevation_class)) + this%nelev = nelev + allocate(this%elevclass_bounds(0:nelev)) this%elevclass_bounds(:) = elevclass_bounds(:) ! (In principle, we could also handle monotonically decreasing elevclass_bounds, but @@ -160,10 +156,9 @@ subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) SHR_ASSERT((size(vertical_gradient) == this%num_points), errMsg(__FILE__, __LINE__)) - if (elevation_class < this%min_elevation_class .or. & - elevation_class > this%max_elevation_class) then + if (elevation_class < 1 .or. elevation_class > this%nelev) then write(logunit,*) subname, ': ERROR: elevation class out of bounds: ', & - elevation_class, this%min_elevation_class, this%max_elevation_class + elevation_class, this%nelev call shr_sys_abort(subname//': ERROR: elevation class out of bounds') end if @@ -172,17 +167,17 @@ subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) ! Start by assuming we're doing a two-sided difference; we'll set this to false if we aren't two_sided = .true. - if (this%min_elevation_class == this%max_elevation_class) then + if (this%nelev == 1) then vertical_gradient(:) = 0._r8 two_sided = .false. else - if (elevation_class == this%min_elevation_class) then + if (elevation_class == 1) then ec_low = elevation_class ec_high = elevation_class + 1 two_sided = .false. - else if (elevation_class == this%max_elevation_class) then + else if (elevation_class == this%nelev) then ec_low = elevation_class - 1 ec_high = elevation_class two_sided = .false. @@ -231,7 +226,7 @@ subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame, elevcla type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data character(len=*) , intent(in) :: fieldname ! base name of the field of interest character(len=*) , intent(in) :: toponame ! base name of the topographic field - character(len=*) , intent(in) :: elevclass_names( this%min_elevation_class: ) ! strings corresponding to each elevation class + character(len=*) , intent(in) :: elevclass_names(:) ! strings corresponding to each elevation class ! ! !LOCAL VARIABLES: integer :: elevclass @@ -246,11 +241,11 @@ subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame, elevcla this%num_points = mct_aVect_lsize(attr_vect) - allocate(this%field(this%num_points, this%min_elevation_class:this%max_elevation_class)) - allocate(this%topo(this%num_points, this%min_elevation_class:this%max_elevation_class)) + allocate(this%field(this%num_points, this%nelev)) + allocate(this%topo(this%num_points, this%nelev)) allocate(temp(this%num_points)) - do elevclass = this%min_elevation_class, this%max_elevation_class + do elevclass = 1, this%nelev fieldname_ec = trim(fieldname) // trim(elevclass_names(elevclass)) call mct_aVect_exportRattr(attr_vect, fieldname_ec, temp) this%field(:,elevclass) = temp(:) @@ -292,8 +287,8 @@ subroutine check_topo(this) character(len=*), parameter :: subname = 'check_topo' !----------------------------------------------------------------------- - do elevclass = this%min_elevation_class, this%max_elevation_class - if (elevclass > this%min_elevation_class) then + do elevclass = 1, this%nelev + if (elevclass > 1) then do i = 1, this%num_points if (this%topo(i,elevclass) - this%elevclass_bounds(elevclass-1) < -tol) then write(logunit,*) subname, ': ERROR: topo lower than lower bound of elevation class:' @@ -304,7 +299,7 @@ subroutine check_topo(this) end do end if - if (elevclass < this%max_elevation_class) then + if (elevclass < this%nelev) then do i = 1, this%num_points if (this%topo(i,elevclass) - this%elevclass_bounds(elevclass) > tol) then write(logunit,*) subname, ': ERROR: topo higher than upper bound of elevation class:' diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index e7238c84379..5ecd2b34067 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -63,8 +63,6 @@ contains attr_vect = this%av, & fieldname = 'data', & toponame = 'topo', & - min_elevation_class = 1, & - max_elevation_class = n_elev_classes, & elevclass_names = elevclass_names(n_elev_classes), & elevclass_bounds = elevclass_bounds) From f65b5cb13f2a130e3c590e25f504146d4e267621 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 29 Apr 2016 08:40:57 -0600 Subject: [PATCH 36/61] Remove need to pass in nelev Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- .../driver/vertical_gradient_calculator_continuous.F90 | 7 +++---- .../test_vertical_gradient_calculator_continuous.pf | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 index b1e17f88776..d0ea5e22af2 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -59,8 +59,7 @@ module vertical_gradient_calculator_continuous !----------------------------------------------------------------------- function constructor(attr_vect, fieldname, toponame, & - nelev, elevclass_names, & - elevclass_bounds) & + elevclass_names, elevclass_bounds) & result(this) ! ! !DESCRIPTION: @@ -87,7 +86,6 @@ function constructor(attr_vect, fieldname, toponame, & type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data character(len=*) , intent(in) :: fieldname ! base name of the field of interest character(len=*) , intent(in) :: toponame ! base name of the topographic field - integer , intent(in) :: nelev ! number of elevation classes (indexing assumed to start at 1) ! strings corresponding to each elevation class character(len=*) , intent(in) :: elevclass_names(:) @@ -98,12 +96,13 @@ function constructor(attr_vect, fieldname, toponame, & real(r8) , intent(in) :: elevclass_bounds(0:) ! ! !LOCAL VARIABLES: + integer :: nelev integer :: pt character(len=*), parameter :: subname = 'constructor' !----------------------------------------------------------------------- - SHR_ASSERT_ALL((ubound(elevclass_names) == (/nelev/)), errMsg(__FILE__, __LINE__)) + nelev = size(elevclass_names) SHR_ASSERT_ALL((ubound(elevclass_bounds) == (/nelev/)), errMsg(__FILE__, __LINE__)) this%nelev = nelev diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf index 94a34211431..4638ce6bf30 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -68,7 +68,6 @@ contains attr_vect = this%av, & fieldname = 'data', & toponame = 'topo', & - nelev = n_elev_classes, & elevclass_names = elevclass_names(n_elev_classes), & elevclass_bounds = elevclass_bounds) From 93c19202a4f732f6aa1ec18d444c417f432390ff Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 29 Apr 2016 09:11:48 -0600 Subject: [PATCH 37/61] Simplify vertical_gradient_calculator_2nd_order constructor Accept actual data rather than an attribute vector Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...vertical_gradient_calculator_2nd_order.F90 | 92 +++---------------- .../vertical_gradient_calculator_factory.F90 | 9 ++ ..._vertical_gradient_calculator_2nd_order.pf | 16 +--- 3 files changed, 26 insertions(+), 91 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index 84d92cd0e3b..344104459c7 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -11,7 +11,6 @@ module vertical_gradient_calculator_2nd_order use seq_comm_mct, only : logunit use vertical_gradient_calculator_base, only : vertical_gradient_calculator_base_type use shr_kind_mod, only : r8 => shr_kind_r8 - use mct_mod use shr_log_mod, only : errMsg => shr_log_errMsg use shr_sys_mod, only : shr_sys_abort @@ -38,7 +37,6 @@ module vertical_gradient_calculator_2nd_order contains procedure :: calc_vertical_gradient - procedure, private :: set_data_from_attr_vect ! extract data from an attribute vector procedure, private :: check_topo ! check topographic heights procedure, private :: limit_gradient @@ -51,13 +49,10 @@ module vertical_gradient_calculator_2nd_order contains !----------------------------------------------------------------------- - function constructor(attr_vect, fieldname, toponame, & - elevclass_names, elevclass_bounds) & - result(this) + function constructor(field, topo, elevclass_bounds) result(this) ! ! !DESCRIPTION: - ! Creates a vertical_gradient_calculator_2nd_order_type object by reading the - ! necessary data from the provided attribute vector. + ! Creates a vertical_gradient_calculator_2nd_order_type object. ! ! Pre-condition: elevclass_bounds must be monotonically increasing. ! @@ -70,43 +65,39 @@ function constructor(attr_vect, fieldname, toponame, & ! limiter.) ! TODO(wjs, 2016-04-21) Currently this pre-condition is not checked: see below. ! - ! The attribute vector is assumed to have fields named fieldname // - ! elevclass_names(1), toponame // elevclass_names(1), etc. - ! ! !USES: ! ! !ARGUMENTS: type(vertical_gradient_calculator_2nd_order_type) :: this ! function result - type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data - character(len=*) , intent(in) :: fieldname ! base name of the field of interest - character(len=*) , intent(in) :: toponame ! base name of the topographic field - - ! strings corresponding to each elevation class - character(len=*) , intent(in) :: elevclass_names(:) + real(r8), intent(in) :: field(:,:) ! field(i,j) is point i, elevation class j + real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j ! bounds of each elevation class; this array should have one more element than the ! number of elevation classes, since it contains lower and upper bounds for each ! elevation class - real(r8) , intent(in) :: elevclass_bounds(0:) + real(r8), intent(in) :: elevclass_bounds(0:) ! ! !LOCAL VARIABLES: - integer :: nelev character(len=*), parameter :: subname = 'constructor' !----------------------------------------------------------------------- - nelev = size(elevclass_names) - SHR_ASSERT_ALL((ubound(elevclass_bounds) == (/nelev/)), errMsg(__FILE__, __LINE__)) + this%num_points = size(field, 1) + this%nelev = size(field, 2) + SHR_ASSERT_ALL((ubound(topo) == (/this%num_points, this%nelev/)), errMsg(__FILE__, __LINE__)) + SHR_ASSERT_ALL((ubound(elevclass_bounds) == (/this%nelev/)), errMsg(__FILE__, __LINE__)) - this%nelev = nelev - allocate(this%elevclass_bounds(0:nelev)) + allocate(this%elevclass_bounds(0:this%nelev)) this%elevclass_bounds(:) = elevclass_bounds(:) ! (In principle, we could also handle monotonically decreasing elevclass_bounds, but ! that would require generalizing some code, such as in check_topo.) call this%check_elevclass_bounds_monotonic_increasing(this%elevclass_bounds) - call this%set_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names) + allocate(this%field(this%num_points, this%nelev)) + this%field(:,:) = field(:,:) + allocate(this%topo(this%num_points, this%nelev)) + this%topo(:,:) = topo(:,:) ! TODO(wjs, 2016-04-21) Uncomment this call to check_topo. It's important for ! topographic heights to be within bounds in order for the limiter to be applied @@ -204,61 +195,6 @@ subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) end subroutine calc_vertical_gradient - !----------------------------------------------------------------------- - subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame, elevclass_names) - ! - ! !DESCRIPTION: - ! Extract data from an attribute vector. - ! - ! Sets this%num_points, and allocates and sets this%field and this%topo. - ! - ! TODO(wjs, 2016-04-26) The current flow is that the constructor calls this - ! routine. It could be better to move this routine into a factory class that creates - ! objects by (1) calling this routine to extract fields from the attribute vector, and - ! then (2) calling the constructor of this class using these extracted data (so the - ! constructor would never need to be passed an attribute vector). - ! - ! !USES: - use mct_mod - ! - ! !ARGUMENTS: - class(vertical_gradient_calculator_2nd_order_type), intent(inout) :: this - type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data - character(len=*) , intent(in) :: fieldname ! base name of the field of interest - character(len=*) , intent(in) :: toponame ! base name of the topographic field - character(len=*) , intent(in) :: elevclass_names(:) ! strings corresponding to each elevation class - ! - ! !LOCAL VARIABLES: - integer :: elevclass - character(len=:), allocatable :: fieldname_ec - character(len=:), allocatable :: toponame_ec - - ! The following temporary array is needed because mct wants pointers - real(r8), pointer :: temp(:) - - character(len=*), parameter :: subname = 'set_data_from_attr_vect' - !----------------------------------------------------------------------- - - this%num_points = mct_aVect_lsize(attr_vect) - - allocate(this%field(this%num_points, this%nelev)) - allocate(this%topo(this%num_points, this%nelev)) - allocate(temp(this%num_points)) - - do elevclass = 1, this%nelev - fieldname_ec = trim(fieldname) // trim(elevclass_names(elevclass)) - call mct_aVect_exportRattr(attr_vect, fieldname_ec, temp) - this%field(:,elevclass) = temp(:) - - toponame_ec = trim(toponame) // trim(elevclass_names(elevclass)) - call mct_aVect_exportRattr(attr_vect, toponame_ec, temp) - this%topo(:,elevclass) = temp(:) - end do - - deallocate(temp) - - end subroutine set_data_from_attr_vect - !----------------------------------------------------------------------- subroutine check_topo(this) ! diff --git a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 index 6c6f1d58c33..5c7b7c5ff51 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 @@ -30,6 +30,9 @@ function create_vertical_gradient_calculator_2nd_order( & ! !DESCRIPTION: ! Creates and returns a vertical_gradient_calculator_2nd_order_type object ! + ! The attribute vector is assumed to have fields named fieldname // + ! elevclass_names(1), toponame // elevclass_names(1), etc. + ! ! !USES: ! ! !ARGUMENTS: @@ -57,6 +60,9 @@ function create_vertical_gradient_calculator_2nd_order( & call extract_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names, & field, topo) + calculator = vertical_gradient_calculator_2nd_order_type( & + field = field, topo = topo, elevclass_bounds = elevclass_bounds) + end function create_vertical_gradient_calculator_2nd_order !----------------------------------------------------------------------- @@ -68,6 +74,9 @@ subroutine extract_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass ! ! Allocates and sets topo_extracted and data_extracted ! + ! The attribute vector is assumed to have fields named fieldname // + ! elevclass_names(1), toponame // elevclass_names(1), etc. + ! ! !USES: ! ! !ARGUMENTS: diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index 5ecd2b34067..7d967dad5cb 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -6,8 +6,6 @@ module test_vertical_gradient_calculator_2nd_order use vertical_gradient_calculator_base use vertical_gradient_calculator_2nd_order use shr_kind_mod, only : r8 => shr_kind_r8 - use mct_mod, only : mct_aVect, mct_aVect_clean - use mct_wrapper_mod, only : mct_init, mct_clean use vertical_gradient_test_utils implicit none @@ -16,7 +14,6 @@ module test_vertical_gradient_calculator_2nd_order @TestCase type, extends(TestCase) :: TestVertGradCalc2ndOrder - type(mct_aVect) :: av contains procedure :: setUp procedure :: tearDown @@ -29,15 +26,11 @@ contains subroutine setUp(this) class(TestVertGradCalc2ndOrder), intent(inout) :: this - call mct_init() - end subroutine setUp subroutine tearDown(this) class(TestVertGradCalc2ndOrder), intent(inout) :: this - call mct_aVect_clean(this%av) - call mct_clean() end subroutine tearDown function create_calculator(this, topo, data, elevclass_bounds) & @@ -55,15 +48,12 @@ contains integer :: n_elev_classes n_elev_classes = size(data,2) + @assertEqual(size(data), size(topo)) @assertEqual(n_elev_classes + 1, size(elevclass_bounds)) - call create_av(topo, data, 'topo', 'data', this%av) - calculator = vertical_gradient_calculator_2nd_order_type( & - attr_vect = this%av, & - fieldname = 'data', & - toponame = 'topo', & - elevclass_names = elevclass_names(n_elev_classes), & + field = data, & + topo = topo, & elevclass_bounds = elevclass_bounds) end function create_calculator From f5c1ce879b43d5c93581a4f784c53d236a5ac740 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 29 Apr 2016 09:23:01 -0600 Subject: [PATCH 38/61] Simplify vertical_gradient_calculator_continuous constructor Take actual data rather than an attribute vector Also add a factory method to create this Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...vertical_gradient_calculator_2nd_order.F90 | 17 +++---- ...ertical_gradient_calculator_continuous.F90 | 48 ++++++++----------- .../vertical_gradient_calculator_factory.F90 | 44 +++++++++++++++++ ...vertical_gradient_calculator_continuous.pf | 15 ++---- 4 files changed, 75 insertions(+), 49 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index 344104459c7..c83c7d4add0 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -56,14 +56,15 @@ function constructor(field, topo, elevclass_bounds) result(this) ! ! Pre-condition: elevclass_bounds must be monotonically increasing. ! - ! Pre-condition: Topographic heights in the attribute vector must all lie inside the - ! bounds of their respective elevation class (given by elevclass_bounds), with the - ! possible exception of the lowest elevation class (topographic heights can lie below - ! the arbitrary lower bound of the elevation class) and the highest elevation class - ! (topographic heights can lie above the arbitrary upper bound of the elevation - ! class). (This pre-condition is mainly important for the sake of calculating the - ! limiter.) - ! TODO(wjs, 2016-04-21) Currently this pre-condition is not checked: see below. + ! Pre-condition: Topographic heights must all lie inside the bounds of their + ! respective elevation class (given by elevclass_bounds), with the possible exception + ! of the lowest elevation class (topographic heights can lie below the arbitrary lower + ! bound of the elevation class) and the highest elevation class (topographic heights + ! can lie above the arbitrary upper bound of the elevation class). (This pre-condition + ! is mainly important for the sake of calculating the limiter.) + ! + ! TODO(wjs, 2016-04-21) Currently the topographic heights pre-condition is not + ! checked: see below. ! ! !USES: ! diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 index d0ea5e22af2..60d69ad0dab 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -12,7 +12,6 @@ module vertical_gradient_calculator_continuous use vertical_gradient_calculator_base, only : vertical_gradient_calculator_base_type use shr_kind_mod, only : r8 => shr_kind_r8 use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) - use mct_mod use shr_log_mod, only : errMsg => shr_log_errMsg use shr_sys_mod, only : shr_sys_abort use shr_matrix_mod, only : tridiagonal_inverse @@ -45,7 +44,6 @@ module vertical_gradient_calculator_continuous procedure :: calc_vertical_gradient procedure, private :: check_topo ! check topographic heights - procedure, private :: set_data_from_attr_vect ! extract data from an attribute vector procedure, private :: precompute_vertical_gradients ! compute vertical gradients for all ECs procedure, private :: solve_for_vertical_gradients ! compute vertical gradients for all ECs, for points where we do a matrix solve @@ -58,37 +56,26 @@ module vertical_gradient_calculator_continuous contains !----------------------------------------------------------------------- - function constructor(attr_vect, fieldname, toponame, & - elevclass_names, elevclass_bounds) & - result(this) + function constructor(field, topo, elevclass_bounds) result(this) ! ! !DESCRIPTION: - ! Creates a vertical_gradient_calculator_continuous_type object by reading the - ! necessary data from the provided attribute vector. + ! Creates a vertical_gradient_calculator_continuous_type object. ! ! Pre-condition: elevclass_bounds must be monotonically increasing. ! - ! Pre-condition: Topographic heights in the attribute vector should all lie inside the - ! bounds of their respective elevation class (given by elevclass_bounds), with the - ! possible exception of the lowest elevation class (topographic heights can lie below - ! the arbitrary lower bound of the elevation class) and the highest elevation class - ! (topographic heights can lie above the arbitrary upper bound of the elevation - ! class). For grid cells where this is not true, sets vertical gradient to 0 for all - ! elevation classes. - ! - ! The attribute vector is assumed to have fields named fieldname // - ! elevclass_names(1), toponame // elevclass_names(1), etc. + ! Pre-condition: Topographic heights should all lie inside the bounds of their + ! respective elevation class (given by elevclass_bounds), with the possible exception + ! of the lowest elevation class (topographic heights can lie below the arbitrary lower + ! bound of the elevation class) and the highest elevation class (topographic heights + ! can lie above the arbitrary upper bound of the elevation class). For grid cells + ! where this is not true, sets vertical gradient to 0 for all elevation classes. ! ! !USES: ! ! !ARGUMENTS: type(vertical_gradient_calculator_continuous_type) :: this ! function result - type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data - character(len=*) , intent(in) :: fieldname ! base name of the field of interest - character(len=*) , intent(in) :: toponame ! base name of the topographic field - - ! strings corresponding to each elevation class - character(len=*) , intent(in) :: elevclass_names(:) + real(r8), intent(in) :: field(:,:) ! field(i,j) is point i, elevation class j + real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j ! bounds of each elevation class; this array should have one more element than the ! number of elevation classes, since it contains lower and upper bounds for each @@ -96,21 +83,24 @@ function constructor(attr_vect, fieldname, toponame, & real(r8) , intent(in) :: elevclass_bounds(0:) ! ! !LOCAL VARIABLES: - integer :: nelev integer :: pt character(len=*), parameter :: subname = 'constructor' !----------------------------------------------------------------------- - nelev = size(elevclass_names) - SHR_ASSERT_ALL((ubound(elevclass_bounds) == (/nelev/)), errMsg(__FILE__, __LINE__)) + this%num_points = size(field, 1) + this%nelev = size(field, 2) + SHR_ASSERT_ALL((ubound(topo) == (/this%num_points, this%nelev/)), errMsg(__FILE__, __LINE__)) + SHR_ASSERT_ALL((ubound(elevclass_bounds) == (/this%nelev/)), errMsg(__FILE__, __LINE__)) - this%nelev = nelev - allocate(this%elevclass_bounds(0:nelev)) + allocate(this%elevclass_bounds(0:this%nelev)) this%elevclass_bounds(:) = elevclass_bounds(:) call this%check_elevclass_bounds_monotonic_increasing(this%elevclass_bounds) - call this%set_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names) + allocate(this%field(this%nelev, this%num_points)) + this%field(:,:) = transpose(field(:,:)) + allocate(this%topo(this%nelev, this%num_points)) + this%topo(:,:) = transpose(topo(:,:)) allocate(this%topo_valid(this%num_points)) call this%check_topo() diff --git a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 index 5c7b7c5ff51..daf229a7876 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 @@ -9,6 +9,7 @@ module vertical_gradient_calculator_factory use shr_kind_mod, only : r8 => shr_kind_r8 use shr_log_mod, only : errMsg => shr_log_errMsg use vertical_gradient_calculator_2nd_order, only : vertical_gradient_calculator_2nd_order_type + use vertical_gradient_calculator_continuous, only : vertical_gradient_calculator_continuous_type use mct_mod implicit none @@ -65,6 +66,49 @@ function create_vertical_gradient_calculator_2nd_order( & end function create_vertical_gradient_calculator_2nd_order + !----------------------------------------------------------------------- + function create_vertical_gradient_calculator_continuous( & + attr_vect, fieldname, toponame, elevclass_names, elevclass_bounds) & + result(calculator) + ! + ! !DESCRIPTION: + ! Creates and returns a vertical_gradient_calculator_continuous_type object + ! + ! The attribute vector is assumed to have fields named fieldname // + ! elevclass_names(1), toponame // elevclass_names(1), etc. + ! + ! !USES: + ! + ! !ARGUMENTS: + type(vertical_gradient_calculator_continuous_type) :: calculator ! function result + type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data + character(len=*) , intent(in) :: fieldname ! base name of the field of interest + character(len=*) , intent(in) :: toponame ! base name of the topographic field + character(len=*) , intent(in) :: elevclass_names(:) ! strings corresponding to each elevation class + ! bounds of each elevation class; this array should have one more element than the + ! number of elevation classes, since it contains lower and upper bounds for each + ! elevation class + real(r8) , intent(in) :: elevclass_bounds(0:) + ! + ! !LOCAL VARIABLES: + integer :: nelev + real(r8), allocatable :: field(:,:) + real(r8), allocatable :: topo(:,:) + + character(len=*), parameter :: subname = 'create_vertical_gradient_calculator_continuous' + !----------------------------------------------------------------------- + + nelev = size(elevclass_names) + SHR_ASSERT_ALL((ubound(elevclass_bounds) == (/nelev/)), errMsg(__FILE__, __LINE__)) + + call extract_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names, & + field, topo) + + calculator = vertical_gradient_calculator_continuous_type( & + field = field, topo = topo, elevclass_bounds = elevclass_bounds) + + end function create_vertical_gradient_calculator_continuous + !----------------------------------------------------------------------- subroutine extract_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names, & field_extracted, topo_extracted) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf index 4638ce6bf30..e7d5fa27bb0 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -5,8 +5,6 @@ module test_vertical_gradient_calculator_continuous use pfunit_mod use vertical_gradient_calculator_continuous use shr_kind_mod , only : r8 => shr_kind_r8 - use mct_mod, only : mct_aVect, mct_aVect_clean - use mct_wrapper_mod, only : mct_init, mct_clean use vertical_gradient_test_utils implicit none @@ -14,7 +12,6 @@ module test_vertical_gradient_calculator_continuous @TestCase type, extends(TestCase) :: TestVertGradCalcCont - type(mct_aVect) :: av contains procedure :: setUp procedure :: tearDown @@ -32,7 +29,6 @@ contains subroutine setUp(this) class(TestVertGradCalcCont), intent(inout) :: this character(len=32) :: filename - call mct_init() test_num = test_num + 1 write(filename, '(a, i0, a)') 'gradients_continuous_', test_num, '.txt' open(out_unit, file=filename, action='write') @@ -40,8 +36,6 @@ contains subroutine tearDown(this) class(TestVertGradCalcCont), intent(inout) :: this - call mct_aVect_clean(this%av) - call mct_clean() close(out_unit) end subroutine tearDown @@ -60,15 +54,12 @@ contains integer :: n_elev_classes n_elev_classes = size(data,2) + @assertEqual(size(data), size(topo)) @assertEqual(n_elev_classes + 1, size(elevclass_bounds)) - call create_av(topo, data, 'topo', 'data', this%av) - calculator = vertical_gradient_calculator_continuous_type( & - attr_vect = this%av, & - fieldname = 'data', & - toponame = 'topo', & - elevclass_names = elevclass_names(n_elev_classes), & + field = data, & + topo = topo, & elevclass_bounds = elevclass_bounds) end function create_calculator From 25bcadbec8d22743ced106469ef894a9e1a83577 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 29 Apr 2016 09:30:38 -0600 Subject: [PATCH 39/61] Move stuff out of vertical_gradient_test_utils Now this stuff is only needed in test_vertical_gradient_calculator_factory.pf Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- .../CMakeLists.txt | 1 - ...st_vertical_gradient_calculator_factory.pf | 75 +++++++++++++++++++ .../test_vertical_gradient_test_utils.pf | 61 --------------- .../vertical_gradient_test_utils.F90 | 57 -------------- 4 files changed, 75 insertions(+), 119 deletions(-) delete mode 100644 driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_test_utils.pf diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt index 501e5265514..d46d0c11bf3 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt @@ -1,5 +1,4 @@ set (pfunit_sources - test_vertical_gradient_test_utils.pf test_vertical_gradient_calculator_2nd_order.pf test_vertical_gradient_calculator_continuous.pf test_vertical_gradient_calculator_factory.pf diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_factory.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_factory.pf index 638c3ef8981..e0bf4095e51 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_factory.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_factory.pf @@ -8,6 +8,7 @@ module test_vertical_gradient_calculator_factory use vertical_gradient_test_utils use mct_mod, only : mct_aVect, mct_aVect_clean use mct_wrapper_mod, only : mct_init, mct_clean + use avect_wrapper_mod implicit none @@ -36,6 +37,80 @@ contains call mct_clean() end subroutine tearDown + function two_digit_string(val) + ! Converts val to a two-digit string + character(len=2) :: two_digit_string + integer, intent(in) :: val + + write(two_digit_string, '(i2.2)') val + end function two_digit_string + + function elevclass_names(n_elev_classes) + ! Returns array of elevation class names + integer, intent(in) :: n_elev_classes + character(len=16) :: elevclass_names(n_elev_classes) + + integer :: i + + do i = 1, n_elev_classes + elevclass_names(i) = two_digit_string(i) + end do + end function elevclass_names + + subroutine create_av(topo, data, toponame, dataname, av) + ! Creates the attribute vector 'av' + real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j + real(r8), intent(in) :: data(:,:) ! data(i,j) is point i, elevation class j + character(len=*), intent(in) :: toponame + character(len=*), intent(in) :: dataname + type(mct_aVect), intent(out) :: av + + integer :: npts + integer :: n_elev_classes + integer :: elevclass + character(len=64), allocatable :: attr_tags(:) + + npts = size(topo, 1) + n_elev_classes = size(topo, 2) + + @assertEqual(ubound(data), [npts, n_elev_classes]) + + allocate(attr_tags(2*n_elev_classes)) + do elevclass = 1, n_elev_classes + attr_tags(elevclass) = dataname // two_digit_string(elevclass) + end do + do elevclass = 1, n_elev_classes + attr_tags(n_elev_classes + elevclass) = toponame // two_digit_string(elevclass) + end do + + call create_aVect_with_data_rows_are_points(av, & + attr_tags = attr_tags, & + data = reshape([data, topo], [npts, n_elev_classes * 2])) + + end subroutine create_av + + @Test + subroutine test_create_av(this) + ! Tests the create_av helper routine + class(TestVertGradCalcFactory), intent(inout) :: this + ! 3 points, 2 elevation classes + real(r8), parameter :: topo(3,2) = reshape( & + [1._r8, 2._r8, 3._r8, & + 4._r8, 5._r8, 6._r8], & + [3, 2]) + real(r8), parameter :: data(3,2) = reshape( & + [11._r8, 12._r8, 13._r8, & + 14._r8, 15._r8, 16._r8], & + [3, 2]) + + call create_av(topo, data, 'topo', 'data', this%av) + + @assertEqual([4._r8, 5._r8, 6._r8], aVect_exportRattr(this%av, 'topo' // two_digit_string(2))) + + @assertEqual([14._r8, 15._r8, 16._r8], aVect_exportRattr(this%av, 'data' // two_digit_string(2))) + + end subroutine test_create_av + @Test subroutine test_extract_data(this) class(TestVertGradCalcFactory), intent(inout) :: this diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_test_utils.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_test_utils.pf deleted file mode 100644 index 6d9b87081e2..00000000000 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_test_utils.pf +++ /dev/null @@ -1,61 +0,0 @@ -module test_vertical_gradient_test_utils - - ! Tests of vertical_gradient_test_utils - - use pfunit_mod - use vertical_gradient_test_utils - use shr_kind_mod , only : r8 => shr_kind_r8 - use mct_mod, only : mct_aVect, mct_aVect_clean - use mct_wrapper_mod, only : mct_init, mct_clean - use avect_wrapper_mod - - implicit none - - @TestCase - type, extends(TestCase) :: TestVertGradTestUtils - type(mct_aVect) :: av - contains - procedure :: setUp - procedure :: tearDown - end type TestVertGradTestUtils - - real(r8), parameter :: tol = 1.e-13_r8 - -contains - - subroutine setUp(this) - class(TestVertGradTestUtils), intent(inout) :: this - - call mct_init() - - end subroutine setUp - - subroutine tearDown(this) - class(TestVertGradTestUtils), intent(inout) :: this - call mct_aVect_clean(this%av) - call mct_clean() - end subroutine tearDown - - @Test - subroutine test_create_av(this) - ! Tests the create_av helper routine - class(TestVertGradTestUtils), intent(inout) :: this - ! 3 points, 2 elevation classes - real(r8), parameter :: topo(3,2) = reshape( & - [1._r8, 2._r8, 3._r8, & - 4._r8, 5._r8, 6._r8], & - [3, 2]) - real(r8), parameter :: data(3,2) = reshape( & - [11._r8, 12._r8, 13._r8, & - 14._r8, 15._r8, 16._r8], & - [3, 2]) - - call create_av(topo, data, 'topo', 'data', this%av) - - @assertEqual([4._r8, 5._r8, 6._r8], aVect_exportRattr(this%av, 'topo' // two_digit_string(2))) - - @assertEqual([14._r8, 15._r8, 16._r8], aVect_exportRattr(this%av, 'data' // two_digit_string(2))) - - end subroutine test_create_av - -end module test_vertical_gradient_test_utils diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 index 7c711499c28..ddb623876ee 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 @@ -5,72 +5,15 @@ module vertical_gradient_test_utils #include "shr_assert.h" use shr_log_mod, only : errMsg => shr_log_errMsg use shr_kind_mod , only : r8 => shr_kind_r8 - use avect_wrapper_mod - use mct_mod, only : mct_aVect use vertical_gradient_calculator_base, only : vertical_gradient_calculator_base_type implicit none private - public :: two_digit_string - public :: elevclass_names - public :: create_av public :: all_gradients_one_point ! Return gradients for all ECs for one point contains - function two_digit_string(val) - ! Converts val to a two-digit string - character(len=2) :: two_digit_string - integer, intent(in) :: val - - write(two_digit_string, '(i2.2)') val - end function two_digit_string - - function elevclass_names(n_elev_classes) - ! Returns array of elevation class names - integer, intent(in) :: n_elev_classes - character(len=16) :: elevclass_names(n_elev_classes) - - integer :: i - - do i = 1, n_elev_classes - elevclass_names(i) = two_digit_string(i) - end do - end function elevclass_names - - subroutine create_av(topo, data, toponame, dataname, av) - ! Creates the attribute vector 'av' - real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j - real(r8), intent(in) :: data(:,:) ! data(i,j) is point i, elevation class j - character(len=*), intent(in) :: toponame - character(len=*), intent(in) :: dataname - type(mct_aVect), intent(out) :: av - - integer :: npts - integer :: n_elev_classes - integer :: elevclass - character(len=64), allocatable :: attr_tags(:) - - npts = size(topo, 1) - n_elev_classes = size(topo, 2) - - SHR_ASSERT_ALL((ubound(data) == (/npts, n_elev_classes/)), errMsg(__FILE__, __LINE__)) - - allocate(attr_tags(2*n_elev_classes)) - do elevclass = 1, n_elev_classes - attr_tags(elevclass) = dataname // two_digit_string(elevclass) - end do - do elevclass = 1, n_elev_classes - attr_tags(n_elev_classes + elevclass) = toponame // two_digit_string(elevclass) - end do - - call create_aVect_with_data_rows_are_points(av, & - attr_tags = attr_tags, & - data = reshape([data, topo], [npts, n_elev_classes * 2])) - - end subroutine create_av - function all_gradients_one_point(calculator, n_elev_classes, npts, pt) result(gradients) ! Return gradients for all ECs for one point class(vertical_gradient_calculator_base_type), intent(in) :: calculator From 58b85357229f964768f3542cf2db3cb626ab797f Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 29 Apr 2016 13:00:50 -0600 Subject: [PATCH 40/61] Separate computation stage of gradient calculators Now there are essentially three separate stages: - create object - compute gradients - get gradients The point of this is to make the different gradient calculators more consistent, and to make it easier to have the new gradient calculator use the 2nd-order gradient calculator, in an upcoming set of changes. Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- driver_cpl/driver/map_lnd2glc_mod.F90 | 5 +- ...vertical_gradient_calculator_2nd_order.F90 | 148 +++++++++++------- .../vertical_gradient_calculator_base.F90 | 24 ++- ...ertical_gradient_calculator_continuous.F90 | 90 ++++++----- .../vertical_gradient_calculator_factory.F90 | 2 +- .../map_lnd2glc_test/test_map_lnd2glc.pf | 26 +-- .../vertical_gradient_calculator_constant.F90 | 66 ++++++-- ..._vertical_gradient_calculator_2nd_order.pf | 38 ++--- ...vertical_gradient_calculator_continuous.pf | 1 + .../vertical_gradient_test_utils.F90 | 2 +- 10 files changed, 255 insertions(+), 147 deletions(-) diff --git a/driver_cpl/driver/map_lnd2glc_mod.F90 b/driver_cpl/driver/map_lnd2glc_mod.F90 index 0a308bb97f4..fa5daa3d14e 100644 --- a/driver_cpl/driver/map_lnd2glc_mod.F90 +++ b/driver_cpl/driver/map_lnd2glc_mod.F90 @@ -83,7 +83,7 @@ subroutine map_lnd2glc(l2x_l, landfrac_l, g2x_g, fieldname, gradient_calculator, type(mct_aVect) , intent(in) :: landfrac_l ! lfrac field on the land grid type(mct_aVect) , intent(in) :: g2x_g ! glc -> cpl fields on the glc grid character(len=*) , intent(in) :: fieldname ! name of the field to map - class(vertical_gradient_calculator_base_type), intent(in) :: gradient_calculator + class(vertical_gradient_calculator_base_type), intent(inout) :: gradient_calculator type(seq_map) , intent(inout) :: mapper type(mct_aVect) , intent(inout) :: l2x_g ! lnd -> cpl fields on the glc grid ! @@ -167,6 +167,7 @@ subroutine map_lnd2glc(l2x_l, landfrac_l, g2x_g, fieldname, gradient_calculator, ! Map ice elevation classes ! ------------------------------------------------------------------------ + call gradient_calculator%calc_gradients() do elevclass = 1, glc_get_num_elevation_classes() call map_one_elevation_class(l2x_l, landfrac_l, fieldname_trimmed, elevclass, & gradient_calculator, glc_topo, mapper, data_g_oneEC) @@ -403,7 +404,7 @@ subroutine map_one_elevation_class(l2x_l, landfrac_l, fieldname, elevclass, & call mct_aVect_exportRattr(l2x_l, fieldname_ec, data_l) call mct_aVect_exportRattr(l2x_l, toponame_ec, topo_l) - call gradient_calculator%calc_vertical_gradient(elevclass, vertical_gradient_l) + call gradient_calculator%get_gradients_one_class(elevclass, vertical_gradient_l) partial_remap_l = data_l - (vertical_gradient_l * topo_l) call mct_aVect_importRattr(l2x_l_temp, partial_remap_tag, partial_remap_l) diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index c83c7d4add0..a8149d22c4e 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -6,6 +6,11 @@ module vertical_gradient_calculator_2nd_order ! ! This module defines a subclass of vertical_gradient_calculator_base_type for ! computing vertical gradients using a second-order centered difference. + ! + ! If the topo values are nearly equal across the gradient (i.e., denominator is near 0), + ! returns a gradient of 0. + ! + ! If there is only one elevation class, returns a gradient of 0. #include "shr_assert.h" use seq_comm_mct, only : logunit @@ -13,6 +18,7 @@ module vertical_gradient_calculator_2nd_order use shr_kind_mod, only : r8 => shr_kind_r8 use shr_log_mod, only : errMsg => shr_log_errMsg use shr_sys_mod, only : shr_sys_abort + use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) implicit none private @@ -34,8 +40,15 @@ module vertical_gradient_calculator_2nd_order ! are guaranteed to be monotonically increasing. real(r8), allocatable :: elevclass_bounds(:) + ! precomputed vertical gradients; vertical_gradient(i,j) is point i, elevation class + ! j + real(r8), allocatable :: vertical_gradient(:,:) + + logical :: calculated ! whether gradients have been calculated yet + contains - procedure :: calc_vertical_gradient + procedure :: calc_gradients + procedure :: get_gradients_one_class procedure, private :: check_topo ! check topographic heights procedure, private :: limit_gradient @@ -83,6 +96,8 @@ function constructor(field, topo, elevclass_bounds) result(this) character(len=*), parameter :: subname = 'constructor' !----------------------------------------------------------------------- + this%calculated = .false. + this%num_points = size(field, 1) this%nelev = size(field, 2) SHR_ASSERT_ALL((ubound(topo) == (/this%num_points, this%nelev/)), errMsg(__FILE__, __LINE__)) @@ -109,92 +124,119 @@ function constructor(field, topo, elevclass_bounds) result(this) ! call this%check_topo() - end function constructor + allocate(this%vertical_gradient(this%num_points, this%nelev)) + this%vertical_gradient(:,:) = nan + end function constructor !----------------------------------------------------------------------- - subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) + subroutine calc_gradients(this) ! ! !DESCRIPTION: - ! Calculates the vertical gradient for all points, at a given elevation class. - ! - ! If the topo values are nearly equal across the gradient (i.e., denominator is near - ! 0), returns a gradient of 0. - ! - ! If there is only one elevation class, returns a gradient of 0. + ! Calculates all vertical gradients ! ! !USES: ! ! !ARGUMENTS: - class(vertical_gradient_calculator_2nd_order_type), intent(in) :: this - integer, intent(in) :: elevation_class - - ! vertical_gradient should already be allocated to the appropriate size - real(r8), intent(out) :: vertical_gradient(:) + class(vertical_gradient_calculator_2nd_order_type), intent(inout) :: this ! ! !LOCAL VARIABLES: ! Tolerance for considering two topo values to be nearly equal real(r8), parameter :: topo_equality_tolerance = 1.e-13_r8 integer :: i + integer :: elevation_class integer :: ec_low ! elevation class index to use as the lower bound of the gradient integer :: ec_high ! elevation class index to use as the upper bound of the gradient logical :: two_sided ! true if we're estimating the gradient with a two-sided difference - character(len=*), parameter :: subname = 'calc_vertical_gradient' + character(len=*), parameter :: subname = 'calc_gradients' !----------------------------------------------------------------------- - ! Assert pre-conditions - - SHR_ASSERT((size(vertical_gradient) == this%num_points), errMsg(__FILE__, __LINE__)) - - if (elevation_class < 1 .or. elevation_class > this%nelev) then - write(logunit,*) subname, ': ERROR: elevation class out of bounds: ', & - elevation_class, this%nelev - call shr_sys_abort(subname//': ERROR: elevation class out of bounds') + if (this%calculated) then + ! nothing to do + return end if - ! Do the calculations - - ! Start by assuming we're doing a two-sided difference; we'll set this to false if we aren't - two_sided = .true. - if (this%nelev == 1) then - vertical_gradient(:) = 0._r8 + this%vertical_gradient(:,:) = 0._r8 two_sided = .false. else - - if (elevation_class == 1) then - ec_low = elevation_class - ec_high = elevation_class + 1 - two_sided = .false. - else if (elevation_class == this%nelev) then - ec_low = elevation_class - 1 - ec_high = elevation_class - two_sided = .false. - else - ec_low = elevation_class - 1 - ec_high = elevation_class + 1 - end if - do i = 1, this%num_points - if (abs(this%topo(i, ec_high) - this%topo(i, ec_low)) < topo_equality_tolerance) then - vertical_gradient(i) = 0._r8 + do elevation_class = 1, this%nelev + if (elevation_class == 1) then + ec_low = elevation_class + ec_high = elevation_class + 1 + two_sided = .false. + else if (elevation_class == this%nelev) then + ec_low = elevation_class - 1 + ec_high = elevation_class + two_sided = .false. else - vertical_gradient(i) = & - (this%field(i, ec_high) - this%field(i, ec_low)) / & - (this%topo (i, ec_high) - this%topo (i, ec_low)) + ec_low = elevation_class - 1 + ec_high = elevation_class + 1 + two_sided = .true. end if - end do - if (two_sided) then - call this%limit_gradient(elevation_class, ec_low, ec_high, vertical_gradient) - end if + do i = 1, this%num_points + if (abs(this%topo(i, ec_high) - this%topo(i, ec_low)) < topo_equality_tolerance) then + this%vertical_gradient(i, elevation_class) = 0._r8 + else + this%vertical_gradient(i, elevation_class) = & + (this%field(i, ec_high) - this%field(i, ec_low)) / & + (this%topo (i, ec_high) - this%topo (i, ec_low)) + end if + end do + + if (two_sided) then + call this%limit_gradient(elevation_class, ec_low, ec_high, & + this%vertical_gradient(:,elevation_class)) + end if + end do end if - end subroutine calc_vertical_gradient + this%calculated = .true. + + end subroutine calc_gradients + + !----------------------------------------------------------------------- + subroutine get_gradients_one_class(this, elevation_class, gradients) + ! + ! !DESCRIPTION: + ! Returns the vertical gradient for all points, at a given elevation class. + ! + ! this%calc_gradients should already have been called + ! + ! !USES: + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_2nd_order_type), intent(in) :: this + integer, intent(in) :: elevation_class + + ! gradients should already be allocated to the appropriate size + real(r8), intent(out) :: gradients(:) + ! + ! !LOCAL VARIABLES: + + character(len=*), parameter :: subname = 'get_gradients_one_class' + !----------------------------------------------------------------------- + + ! Assert pre-conditions + + SHR_ASSERT(this%calculated, errMsg(__FILE__, __LINE__)) + SHR_ASSERT((size(gradients) == this%num_points), errMsg(__FILE__, __LINE__)) + + if (elevation_class < 1 .or. elevation_class > this%nelev) then + write(logunit,*) subname, ': ERROR: elevation class out of bounds: ', & + elevation_class, this%nelev + call shr_sys_abort(subname//': ERROR: elevation class out of bounds') + end if + + gradients(:) = this%vertical_gradient(:, elevation_class) + + end subroutine get_gradients_one_class !----------------------------------------------------------------------- subroutine check_topo(this) diff --git a/driver_cpl/driver/vertical_gradient_calculator_base.F90 b/driver_cpl/driver/vertical_gradient_calculator_base.F90 index 1261ecb9133..fef5a3085a0 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_base.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_base.F90 @@ -6,6 +6,12 @@ module vertical_gradient_calculator_base ! ! This module defines an abstract base class for computing the vertical gradient of a ! field. + ! + ! Usage: + ! + ! - First call calc_gradients + ! + ! - Then can query the computed vertical gradients using the other methods use seq_comm_mct, only : logunit use shr_kind_mod, only : r8 => shr_kind_r8 @@ -18,8 +24,11 @@ module vertical_gradient_calculator_base type, abstract :: vertical_gradient_calculator_base_type contains - ! Calculate the vertical gradient for all points, for a given elevation class - procedure(calc_vertical_gradient_interface), deferred :: calc_vertical_gradient + ! Calculate the vertical gradients for all points and all elevation classes + procedure(calc_gradients_interface), deferred :: calc_gradients + + ! Get the vertical gradients for all points for a single elevation class + procedure(get_gradients_one_class_interface), deferred :: get_gradients_one_class ! These routines are utility methods for derived classes; they should not be called ! by clients of this class. @@ -29,15 +38,20 @@ module vertical_gradient_calculator_base end type vertical_gradient_calculator_base_type abstract interface - subroutine calc_vertical_gradient_interface(this, elevation_class, vertical_gradient) + subroutine calc_gradients_interface(this) + import :: vertical_gradient_calculator_base_type + class(vertical_gradient_calculator_base_type), intent(inout) :: this + end subroutine calc_gradients_interface + + subroutine get_gradients_one_class_interface(this, elevation_class, gradients) import :: vertical_gradient_calculator_base_type import :: r8 class(vertical_gradient_calculator_base_type), intent(in) :: this integer, intent(in) :: elevation_class ! vertical_gradient should already be allocated to the appropriate size - real(r8), intent(out) :: vertical_gradient(:) - end subroutine calc_vertical_gradient_interface + real(r8), intent(out) :: gradients(:) + end subroutine get_gradients_one_class_interface end interface contains diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 index 60d69ad0dab..29c7f06c3f0 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -40,11 +40,13 @@ module vertical_gradient_calculator_continuous ! increasing. real(r8), allocatable :: elevclass_bounds(:) + logical :: calculated ! whether gradients have been calculated yet + contains - procedure :: calc_vertical_gradient + procedure :: calc_gradients + procedure :: get_gradients_one_class procedure, private :: check_topo ! check topographic heights - procedure, private :: precompute_vertical_gradients ! compute vertical gradients for all ECs procedure, private :: solve_for_vertical_gradients ! compute vertical gradients for all ECs, for points where we do a matrix solve end type vertical_gradient_calculator_continuous_type @@ -83,11 +85,12 @@ function constructor(field, topo, elevclass_bounds) result(this) real(r8) , intent(in) :: elevclass_bounds(0:) ! ! !LOCAL VARIABLES: - integer :: pt character(len=*), parameter :: subname = 'constructor' !----------------------------------------------------------------------- + this%calculated = .false. + this%num_points = size(field, 1) this%nelev = size(field, 2) SHR_ASSERT_ALL((ubound(topo) == (/this%num_points, this%nelev/)), errMsg(__FILE__, __LINE__)) @@ -108,39 +111,66 @@ function constructor(field, topo, elevclass_bounds) result(this) allocate(this%vertical_gradient(this%nelev, this%num_points)) this%vertical_gradient(:,:) = nan - ! For this implementation of the vertical gradient calculator, we compute all vertical - ! gradients in object construction. This is because we compute them all simultaneously - ! rather than independently. (So then, the call to the routine that would normally - ! compute vertical gradients for one elevation class simply returns the pre-computed - ! vertical gradients for that elevation class.) + end function constructor + + !----------------------------------------------------------------------- + subroutine calc_gradients(this) + ! + ! !DESCRIPTION: + ! + ! + ! !USES: + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_continuous_type), intent(inout) :: this + ! + ! !LOCAL VARIABLES: + integer :: pt + + character(len=*), parameter :: subname = 'calc_gradients' + !----------------------------------------------------------------------- + + if (this%calculated) then + return + end if do pt = 1, this%num_points - call this%precompute_vertical_gradients(pt) + if (.not. this%topo_valid(pt)) then + this%vertical_gradient(:,pt) = 0._r8 + else + call this%solve_for_vertical_gradients(pt) + end if end do - end function constructor + this%calculated = .true. + + end subroutine calc_gradients + !----------------------------------------------------------------------- - subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) + subroutine get_gradients_one_class(this, elevation_class, gradients) ! ! !DESCRIPTION: ! Returns the vertical gradient for all points, at a given elevation class. ! + ! this%calc_gradients should already have been called + ! ! !USES: ! ! !ARGUMENTS: class(vertical_gradient_calculator_continuous_type), intent(in) :: this integer, intent(in) :: elevation_class - ! vertical_gradient should already be allocated to the appropriate size - real(r8), intent(out) :: vertical_gradient(:) + ! gradients should already be allocated to the appropriate size + real(r8), intent(out) :: gradients(:) ! ! !LOCAL VARIABLES: - character(len=*), parameter :: subname = 'calc_vertical_gradient' + character(len=*), parameter :: subname = 'get_gradients_one_class' !----------------------------------------------------------------------- - SHR_ASSERT((size(vertical_gradient) == this%num_points), errMsg(__FILE__, __LINE__)) + SHR_ASSERT(this%calculated, errMsg(__FILE__, __LINE__)) + SHR_ASSERT((size(gradients) == this%num_points), errMsg(__FILE__, __LINE__)) if (elevation_class < 1 .or. & elevation_class > this%nelev) then @@ -149,9 +179,9 @@ subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) call shr_sys_abort(subname//': ERROR: elevation class out of bounds') end if - vertical_gradient(:) = this%vertical_gradient(elevation_class, :) + gradients(:) = this%vertical_gradient(elevation_class, :) - end subroutine calc_vertical_gradient + end subroutine get_gradients_one_class !----------------------------------------------------------------------- @@ -258,32 +288,6 @@ subroutine check_topo(this) end subroutine check_topo - !----------------------------------------------------------------------- - subroutine precompute_vertical_gradients(this, pt) - ! - ! !DESCRIPTION: - ! Compute and save vertical gradients for all elevation classes in this point. - ! - ! !USES: - ! - ! !ARGUMENTS: - class(vertical_gradient_calculator_continuous_type), intent(inout) :: this - integer, intent(in) :: pt ! point to compute gradients for (1..this%num_points) - ! - ! !LOCAL VARIABLES: - - character(len=*), parameter :: subname = 'precompute_vertical_gradients' - !----------------------------------------------------------------------- - - if (.not. this%topo_valid(pt)) then - this%vertical_gradient(:,pt) = 0._r8 - else - call this%solve_for_vertical_gradients(pt) - end if - - end subroutine precompute_vertical_gradients - - !----------------------------------------------------------------------- subroutine solve_for_vertical_gradients(this, pt) ! diff --git a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 index daf229a7876..3e9244d4aa8 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 @@ -29,7 +29,7 @@ function create_vertical_gradient_calculator_2nd_order( & result(calculator) ! ! !DESCRIPTION: - ! Creates and returns a vertical_gradient_calculator_2nd_order_type object + ! Creates and returns a vertical_gradient_calculator_2nd_order_type object. ! ! The attribute vector is assumed to have fields named fieldname // ! elevclass_names(1), toponame // elevclass_names(1), etc. diff --git a/driver_cpl/unit_test/map_lnd2glc_test/test_map_lnd2glc.pf b/driver_cpl/unit_test/map_lnd2glc_test/test_map_lnd2glc.pf index c81ed01aab3..ff6f2e4d936 100644 --- a/driver_cpl/unit_test/map_lnd2glc_test/test_map_lnd2glc.pf +++ b/driver_cpl/unit_test/map_lnd2glc_test/test_map_lnd2glc.pf @@ -148,7 +148,7 @@ contains ! This utility function wraps the call to the map_lnd2glc routine class(TestMapLnd2glc), intent(inout) :: this - class(vertical_gradient_calculator_base_type), intent(in) :: gradient_calculator + class(vertical_gradient_calculator_base_type), intent(inout) :: gradient_calculator ! Name of field to map. If not provided, uses 'data'. (This argument is available to ! test particular cases, such as having trailing blanks in the fieldname; for most @@ -198,7 +198,8 @@ contains my_map = create_simple_map_with_one_source(ndest = 1) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 1, gradient = 2._r8) + vertical_gradient_calculator_constant_type(num_points = 1, & + nelev = n_elev_classes, gradient = 2._r8) ! data in elev class: 0 1 2 3 lnd_data%topo(:) = [25._r8, 50._r8, 150._r8, 250._r8] @@ -240,7 +241,8 @@ contains my_map = create_simple_map_with_one_source(ndest = npts_glc) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 1, gradient = 0._r8) + vertical_gradient_calculator_constant_type(num_points = 1, & + nelev = n_elev_classes, gradient = 0._r8) ! data in elev class: 0 1 2 3 lnd_data%topo(:) = [25._r8, 50._r8, 150._r8, 250._r8] @@ -287,7 +289,8 @@ contains my_map = create_simple_map_with_one_source(ndest = npts_glc) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 1, gradient = 0._r8) + vertical_gradient_calculator_constant_type(num_points = 1, & + nelev = n_elev_classes, gradient = 0._r8) ! data in elev class: 0 1 2 3 lnd_data%topo(:) = [25._r8, 50._r8, 150._r8, 250._r8] @@ -337,7 +340,8 @@ contains my_map = create_simple_map_with_one_source(ndest = npts_glc) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 1, gradient = 0._r8) + vertical_gradient_calculator_constant_type(num_points = 1, & + nelev = n_elev_classes, gradient = 0._r8) ! data in elev class: 0 1 2 3 lnd_data%topo(:) = [25._r8, 50._r8, 150._r8, 250._r8] @@ -467,7 +471,8 @@ contains overlap_weights = [1._r8, 1._r8, 0.4_r8, 0.6_r8]) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 2, gradient = 2._r8) + vertical_gradient_calculator_constant_type(num_points = 2, & + nelev = n_elev_classes, gradient = 2._r8) ! data in elev class: 0 1 2 3 lnd_data(1)%topo(:) = [25._r8, 50._r8, 150._r8, 250._r8] @@ -534,7 +539,8 @@ contains overlap_weights = lnd_overlaps) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 2, gradient = 0._r8) + vertical_gradient_calculator_constant_type(num_points = 2, & + nelev = n_elev_classes, gradient = 0._r8) ! data in elev class: 0 1 2 3 lnd_data(1)%topo(:) = [25._r8, 50._r8, 150._r8, 250._r8] @@ -593,7 +599,8 @@ contains overlap_weights = lnd_overlaps) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 2, gradient = 0._r8) + vertical_gradient_calculator_constant_type(num_points = 2, & + nelev = n_elev_classes, gradient = 0._r8) ! data in elev class: 0 1 2 3 lnd_data(1)%topo(:) = [25._r8, 50._r8, 150._r8, 250._r8] @@ -667,7 +674,8 @@ contains overlap_weights = [overlaps_with_lnd1, overlaps_with_lnd2]) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 2, gradient = 2._r8) + vertical_gradient_calculator_constant_type(num_points = 2, & + nelev = n_elev_classes, gradient = 2._r8) areas_lnd1(:) = overlaps_with_lnd1(:) * area_glc(:) areas_lnd2(:) = overlaps_with_lnd2(:) * area_glc(:) diff --git a/driver_cpl/unit_test/stubs/vertical_gradient_calculator_constant.F90 b/driver_cpl/unit_test/stubs/vertical_gradient_calculator_constant.F90 index 2161b2df171..f80aecd39dd 100644 --- a/driver_cpl/unit_test/stubs/vertical_gradient_calculator_constant.F90 +++ b/driver_cpl/unit_test/stubs/vertical_gradient_calculator_constant.F90 @@ -11,6 +11,8 @@ module vertical_gradient_calculator_constant #include "shr_assert.h" use vertical_gradient_calculator_base, only : vertical_gradient_calculator_base_type use shr_kind_mod , only : r8 => shr_kind_r8 + use shr_sys_mod, only : shr_sys_abort + use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) implicit none private @@ -21,9 +23,13 @@ module vertical_gradient_calculator_constant vertical_gradient_calculator_constant_type private integer :: num_points + integer :: nelev real(r8) :: gradient + real(r8), allocatable :: vertical_gradient(:,:) ! [point, elev classs] + logical :: calculated contains - procedure :: calc_vertical_gradient + procedure :: calc_gradients + procedure :: get_gradients_one_class end type vertical_gradient_calculator_constant_type interface vertical_gradient_calculator_constant_type @@ -33,7 +39,7 @@ module vertical_gradient_calculator_constant contains !----------------------------------------------------------------------- - function constructor(num_points, gradient) result(this) + function constructor(num_points, nelev, gradient) result(this) ! ! !DESCRIPTION: ! Create a new vertical_gradient_calculator_constant_type object. @@ -49,6 +55,7 @@ function constructor(num_points, gradient) result(this) ! !ARGUMENTS: type(vertical_gradient_calculator_constant_type) :: this ! function result integer, intent(in) :: num_points + integer, intent(in) :: nelev real(r8), intent(in) :: gradient ! ! !LOCAL VARIABLES: @@ -56,13 +63,48 @@ function constructor(num_points, gradient) result(this) character(len=*), parameter :: subname = 'constructor' !----------------------------------------------------------------------- + this%calculated = .false. this%num_points = num_points + this%nelev = nelev this%gradient = gradient - + + allocate(this%vertical_gradient(num_points, nelev)) + this%vertical_gradient(:,:) = nan + end function constructor !----------------------------------------------------------------------- - subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) + subroutine calc_gradients(this) + ! + ! !DESCRIPTION: + ! Calculate the vertical gradients + ! + ! !USES: + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_constant_type), intent(inout) :: this + ! + ! !LOCAL VARIABLES: + integer :: pt, ec + + character(len=*), parameter :: subname = 'calc_gradients' + !----------------------------------------------------------------------- + + SHR_ASSERT(.not. this%calculated, 'gradients already calculated') + + do ec = 1, this%nelev + do pt = 1, this%num_points + this%vertical_gradient(pt, ec) = this%gradient * ec * pt**2 + end do + end do + + this%calculated = .true. + + end subroutine calc_gradients + + + !----------------------------------------------------------------------- + subroutine get_gradients_one_class(this, elevation_class, gradients) ! ! !DESCRIPTION: ! Calculate the vertical gradient for all points @@ -73,20 +115,20 @@ subroutine calc_vertical_gradient(this, elevation_class, vertical_gradient) class(vertical_gradient_calculator_constant_type), intent(in) :: this integer, intent(in) :: elevation_class - ! vertical_gradient should already be allocated to the appropriate size - real(r8), intent(out) :: vertical_gradient(:) + ! gradients should already be allocated to the appropriate size + real(r8), intent(out) :: gradients(:) ! ! !LOCAL VARIABLES: integer :: grid_cell - character(len=*), parameter :: subname = 'calc_vertical_gradient' + character(len=*), parameter :: subname = 'get_gradients_one_class' !----------------------------------------------------------------------- - SHR_ASSERT((size(vertical_gradient) == this%num_points), subname//': wrong size for vertical gradient') + SHR_ASSERT(this%calculated, 'gradients not yet calculated') + SHR_ASSERT(elevation_class <= this%nelev, subname//': elevation class exceeds bounds') + SHR_ASSERT((size(gradients) == this%num_points), subname//': wrong size for vertical gradient') - do grid_cell = 1, this%num_points - vertical_gradient(grid_cell) = this%gradient * elevation_class * grid_cell**2 - end do - end subroutine calc_vertical_gradient + gradients(:) = this%vertical_gradient(:, elevation_class) + end subroutine get_gradients_one_class end module vertical_gradient_calculator_constant diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index 7d967dad5cb..6c8555c5a00 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -55,6 +55,7 @@ contains field = data, & topo = topo, & elevclass_bounds = elevclass_bounds) + call calculator%calc_gradients() end function create_calculator @@ -86,7 +87,7 @@ contains elevclass_bounds = elevclass_bounds) ! Exercise - call calculator%calc_vertical_gradient(2, vertical_gradient) + call calculator%get_gradients_one_class(2, vertical_gradient) ! Verify @assertEqual(expected_vertical_gradient, vertical_gradient(1), tolerance=tol, message = msg) @@ -94,8 +95,8 @@ contains @Test subroutine ECmid(this) - ! Test calc_vertical_gradient with an elevation class in the middle of the range - ! (standard case, not an edge case). This uses a single grid cell. + ! Test with an elevation class in the middle of the range (standard case, not an edge + ! case). This uses a single grid cell. class(TestVertGradCalc2ndOrder), intent(inout) :: this real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] @@ -189,8 +190,7 @@ contains @Test subroutine ECbottom(this) - ! Test calc_vertical_gradient with an elevation class at the bottom edge. This uses a - ! single grid cell. + ! Test with an elevation class at the bottom edge. This uses a single grid cell. class(TestVertGradCalc2ndOrder), intent(inout) :: this type(vertical_gradient_calculator_2nd_order_type) :: calculator real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] @@ -202,7 +202,7 @@ contains calculator = this%create_calculator(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - call calculator%calc_vertical_gradient(1, vertical_gradient) + call calculator%get_gradients_one_class(1, vertical_gradient) expected_vertical_gradient(1) = (data(1,2) - data(1,1)) / (topo(1,2) - topo(1,1)) @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) @@ -211,8 +211,7 @@ contains @Test subroutine ECtop(this) - ! Test calc_vertical_gradient with an elevation class at the top edge. This uses a - ! single grid cell. + ! Test with an elevation class at the top edge. This uses a single grid cell. class(TestVertGradCalc2ndOrder), intent(inout) :: this type(vertical_gradient_calculator_2nd_order_type) :: calculator real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] @@ -224,7 +223,7 @@ contains calculator = this%create_calculator(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - call calculator%calc_vertical_gradient(3, vertical_gradient) + call calculator%get_gradients_one_class(3, vertical_gradient) expected_vertical_gradient(1) = (data(1,3) - data(1,2)) / (topo(1,3) - topo(1,2)) @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) @@ -233,8 +232,7 @@ contains @Test subroutine OneEC(this) - ! Test calc_vertical_gradient with a single elevation class. This uses a single grid - ! cell. + ! Test with a single elevation class. This uses a single grid cell. class(TestVertGradCalc2ndOrder), intent(inout) :: this type(vertical_gradient_calculator_2nd_order_type) :: calculator real(r8), parameter :: elevclass_bounds(2) = [0._r8, 100._r8] @@ -246,7 +244,7 @@ contains calculator = this%create_calculator(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - call calculator%calc_vertical_gradient(1, vertical_gradient) + call calculator%get_gradients_one_class(1, vertical_gradient) expected_vertical_gradient(1) = 0._r8 @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) @@ -255,8 +253,7 @@ contains @Test subroutine toposEqual(this) - ! Test calc_vertical_gradient with topo values equal - make sure this edge case is - ! handled correctly. + ! Test with topo values equal - make sure this edge case is handled correctly. class(TestVertGradCalc2ndOrder), intent(inout) :: this type(vertical_gradient_calculator_2nd_order_type) :: calculator real(r8), parameter :: elevclass_bounds(3) = [0._r8, 100._r8, 200._r8] @@ -268,7 +265,7 @@ contains calculator = this%create_calculator(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - call calculator%calc_vertical_gradient(2, vertical_gradient) + call calculator%get_gradients_one_class(2, vertical_gradient) expected_vertical_gradient(1) = 0._r8 @assertEqual(expected_vertical_gradient, vertical_gradient, tolerance=tol) @@ -420,8 +417,8 @@ contains calculator = this%create_calculator(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - call calculator%calc_vertical_gradient(2, vertical_gradient_ec2) - call calculator%calc_vertical_gradient(3, vertical_gradient_ec3) + call calculator%get_gradients_one_class(2, vertical_gradient_ec2) + call calculator%get_gradients_one_class(3, vertical_gradient_ec3) ! Show non-monotonicity in two ways: @@ -446,8 +443,7 @@ contains @Test subroutine multiplePoints(this) - ! Test calc_vertical_gradient with multiple grid cells. One has topo values equal, - ! two are normal cases. + ! Test with multiple grid cells. One has topo values equal, two are normal cases. class(TestVertGradCalc2ndOrder), intent(inout) :: this type(vertical_gradient_calculator_2nd_order_type) :: calculator @@ -471,7 +467,7 @@ contains calculator = this%create_calculator(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - call calculator%calc_vertical_gradient(2, vertical_gradient) + call calculator%get_gradients_one_class(2, vertical_gradient) expected_vertical_gradient(1) = (data(1,2) - data(1,1)) / (topo(1,2) - topo(1,1)) expected_vertical_gradient(2) = 0._r8 @@ -509,7 +505,7 @@ contains calculator = this%create_calculator(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - call calculator%calc_vertical_gradient(2, vertical_gradient) + call calculator%get_gradients_one_class(2, vertical_gradient) expected_vertical_gradient(1) = 1._r8/25._r8 expected_vertical_gradient(2) = 2._r8/225._r8 diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf index e7d5fa27bb0..f865b11f3fd 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -61,6 +61,7 @@ contains field = data, & topo = topo, & elevclass_bounds = elevclass_bounds) + call calculator%calc_gradients() end function create_calculator diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 index ddb623876ee..a01e15a4e15 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 @@ -26,7 +26,7 @@ function all_gradients_one_point(calculator, n_elev_classes, npts, pt) result(gr real(r8) :: gradients_one_ec(npts) do ec = 1, n_elev_classes - call calculator%calc_vertical_gradient(ec, gradients_one_ec) + call calculator%get_gradients_one_class(ec, gradients_one_ec) gradients(ec) = gradients_one_ec(pt) end do end function all_gradients_one_point From 5caac6064a1d90400211fb46f479c821c506e736 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 29 Apr 2016 13:14:09 -0600 Subject: [PATCH 41/61] Change the stub calculator to allow specifying any gradients Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- .../map_lnd2glc_test/test_map_lnd2glc.pf | 39 ++++----- .../vertical_gradient_calculator_constant.F90 | 85 +++++++++++++------ 2 files changed, 78 insertions(+), 46 deletions(-) diff --git a/driver_cpl/unit_test/map_lnd2glc_test/test_map_lnd2glc.pf b/driver_cpl/unit_test/map_lnd2glc_test/test_map_lnd2glc.pf index ff6f2e4d936..b7ef31e2ab0 100644 --- a/driver_cpl/unit_test/map_lnd2glc_test/test_map_lnd2glc.pf +++ b/driver_cpl/unit_test/map_lnd2glc_test/test_map_lnd2glc.pf @@ -12,7 +12,8 @@ module test_map_lnd2glc use simple_map_mod use create_mapper_mod use vertical_gradient_calculator_base, only : vertical_gradient_calculator_base_type - use vertical_gradient_calculator_constant, only : vertical_gradient_calculator_constant_type + use vertical_gradient_calculator_specified, only : & + vertical_gradient_calculator_specified_type, vgc_specified_ec_times_ptSquared use shr_kind_mod, only : r8 => shr_kind_r8 implicit none @@ -189,7 +190,7 @@ contains ! complex. type(simple_map_type), intent(out) :: my_map - type(vertical_gradient_calculator_constant_type), intent(out) :: gradient_calculator + type(vertical_gradient_calculator_specified_type), intent(out) :: gradient_calculator type(lnd_data_type), intent(out) :: lnd_data real(r8), intent(out) :: frac_glc(1) real(r8), intent(out) :: topo_glc(1) @@ -198,7 +199,7 @@ contains my_map = create_simple_map_with_one_source(ndest = 1) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 1, & + vgc_specified_ec_times_ptSquared(num_points = 1, & nelev = n_elev_classes, gradient = 2._r8) ! data in elev class: 0 1 2 3 @@ -232,7 +233,7 @@ contains real(r8) :: expected_data_glc(npts_glc) type(simple_map_type) :: my_map - type(vertical_gradient_calculator_constant_type) :: gradient_calculator + type(vertical_gradient_calculator_specified_type) :: gradient_calculator ! ------------------------------------------------------------------------ ! Setup @@ -241,7 +242,7 @@ contains my_map = create_simple_map_with_one_source(ndest = npts_glc) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 1, & + vgc_specified_ec_times_ptSquared(num_points = 1, & nelev = n_elev_classes, gradient = 0._r8) ! data in elev class: 0 1 2 3 @@ -280,7 +281,7 @@ contains real(r8) :: expected_data_glc(npts_glc) type(simple_map_type) :: my_map - type(vertical_gradient_calculator_constant_type) :: gradient_calculator + type(vertical_gradient_calculator_specified_type) :: gradient_calculator ! ------------------------------------------------------------------------ ! Setup @@ -289,7 +290,7 @@ contains my_map = create_simple_map_with_one_source(ndest = npts_glc) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 1, & + vgc_specified_ec_times_ptSquared(num_points = 1, & nelev = n_elev_classes, gradient = 0._r8) ! data in elev class: 0 1 2 3 @@ -331,7 +332,7 @@ contains real(r8) :: expected_data_glc(npts_glc) type(simple_map_type) :: my_map - type(vertical_gradient_calculator_constant_type) :: gradient_calculator + type(vertical_gradient_calculator_specified_type) :: gradient_calculator ! ------------------------------------------------------------------------ ! Setup @@ -340,7 +341,7 @@ contains my_map = create_simple_map_with_one_source(ndest = npts_glc) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 1, & + vgc_specified_ec_times_ptSquared(num_points = 1, & nelev = n_elev_classes, gradient = 0._r8) ! data in elev class: 0 1 2 3 @@ -379,7 +380,7 @@ contains real(r8) :: expected_data_glc(npts_glc) type(simple_map_type) :: my_map - type(vertical_gradient_calculator_constant_type) :: gradient_calculator + type(vertical_gradient_calculator_specified_type) :: gradient_calculator ! ------------------------------------------------------------------------ ! Setup @@ -416,7 +417,7 @@ contains real(r8) :: expected_data_glc(npts_glc) type(simple_map_type) :: my_map - type(vertical_gradient_calculator_constant_type) :: gradient_calculator + type(vertical_gradient_calculator_specified_type) :: gradient_calculator ! ------------------------------------------------------------------------ ! Setup @@ -458,7 +459,7 @@ contains real(r8) :: expected_data_glc(npts_glc) type(simple_map_type) :: my_map - type(vertical_gradient_calculator_constant_type) :: gradient_calculator + type(vertical_gradient_calculator_specified_type) :: gradient_calculator ! ------------------------------------------------------------------------ @@ -471,7 +472,7 @@ contains overlap_weights = [1._r8, 1._r8, 0.4_r8, 0.6_r8]) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 2, & + vgc_specified_ec_times_ptSquared(num_points = 2, & nelev = n_elev_classes, gradient = 2._r8) ! data in elev class: 0 1 2 3 @@ -527,7 +528,7 @@ contains real(r8) :: expected_data_glc(npts_glc) type(simple_map_type) :: my_map - type(vertical_gradient_calculator_constant_type) :: gradient_calculator + type(vertical_gradient_calculator_specified_type) :: gradient_calculator ! ------------------------------------------------------------------------ ! Setup @@ -539,7 +540,7 @@ contains overlap_weights = lnd_overlaps) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 2, & + vgc_specified_ec_times_ptSquared(num_points = 2, & nelev = n_elev_classes, gradient = 0._r8) ! data in elev class: 0 1 2 3 @@ -587,7 +588,7 @@ contains real(r8) :: expected_data_glc(npts_glc) type(simple_map_type) :: my_map - type(vertical_gradient_calculator_constant_type) :: gradient_calculator + type(vertical_gradient_calculator_specified_type) :: gradient_calculator ! ------------------------------------------------------------------------ ! Setup @@ -599,7 +600,7 @@ contains overlap_weights = lnd_overlaps) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 2, & + vgc_specified_ec_times_ptSquared(num_points = 2, & nelev = n_elev_classes, gradient = 0._r8) ! data in elev class: 0 1 2 3 @@ -649,7 +650,7 @@ contains real(r8) :: expected_data_glc(npts_glc) type(simple_map_type) :: my_map - type(vertical_gradient_calculator_constant_type) :: gradient_calculator + type(vertical_gradient_calculator_specified_type) :: gradient_calculator ! These parameters give the fraction of each glc cell that is in lnd1 and the ! fraction in lnd2 (note that this sums to 1 for each glc point) @@ -674,7 +675,7 @@ contains overlap_weights = [overlaps_with_lnd1, overlaps_with_lnd2]) gradient_calculator = & - vertical_gradient_calculator_constant_type(num_points = 2, & + vgc_specified_ec_times_ptSquared(num_points = 2, & nelev = n_elev_classes, gradient = 2._r8) areas_lnd1(:) = overlaps_with_lnd1(:) * area_glc(:) diff --git a/driver_cpl/unit_test/stubs/vertical_gradient_calculator_constant.F90 b/driver_cpl/unit_test/stubs/vertical_gradient_calculator_constant.F90 index f80aecd39dd..de5be877d2b 100644 --- a/driver_cpl/unit_test/stubs/vertical_gradient_calculator_constant.F90 +++ b/driver_cpl/unit_test/stubs/vertical_gradient_calculator_constant.F90 @@ -1,11 +1,16 @@ -module vertical_gradient_calculator_constant +module vertical_gradient_calculator_specified !--------------------------------------------------------------------- ! ! Purpose: ! ! This module defines a subclass of vertical_gradient_calculator_base_type that is - ! useful for unit testing. It computes the gradient as a constant times the elevation + ! useful for unit testing. It returns a specified vertical gradient. + ! + ! This module also provides convenience functions for creating a + ! vertical_gradient_calculator_specified_type object with various functional forms. + + ! It computes the gradient as a constant times the elevation ! class index times (the grid cell index squared). #include "shr_assert.h" @@ -17,34 +22,34 @@ module vertical_gradient_calculator_constant implicit none private - public :: vertical_gradient_calculator_constant_type + public :: vertical_gradient_calculator_specified_type type, extends(vertical_gradient_calculator_base_type) :: & - vertical_gradient_calculator_constant_type + vertical_gradient_calculator_specified_type private integer :: num_points integer :: nelev - real(r8) :: gradient real(r8), allocatable :: vertical_gradient(:,:) ! [point, elev classs] logical :: calculated contains procedure :: calc_gradients procedure :: get_gradients_one_class - end type vertical_gradient_calculator_constant_type + end type vertical_gradient_calculator_specified_type - interface vertical_gradient_calculator_constant_type + interface vertical_gradient_calculator_specified_type module procedure constructor - end interface vertical_gradient_calculator_constant_type + end interface vertical_gradient_calculator_specified_type + ! Creates a calculator where the gradient in ec i, pt j is gradient * i * j^2 + public :: vgc_specified_ec_times_ptSquared contains !----------------------------------------------------------------------- - function constructor(num_points, nelev, gradient) result(this) + function vgc_specified_ec_times_ptSquared(num_points, nelev, gradient) & + result(calculator) ! ! !DESCRIPTION: - ! Create a new vertical_gradient_calculator_constant_type object. - ! - ! The returned gradient will be (gradient) * (elevation_class) * (grid_cell_index)^2 + ! Creates a calculator where the gradient in ec i, pt j is gradient * i * j^2 ! ! num_points gives the number of points for which a gradient is needed (e.g., if ! computing the vertical gradient on the land domain, then num_points is the number @@ -53,23 +58,54 @@ function constructor(num_points, nelev, gradient) result(this) ! !USES: ! ! !ARGUMENTS: - type(vertical_gradient_calculator_constant_type) :: this ! function result + type(vertical_gradient_calculator_specified_type) :: calculator ! function result integer, intent(in) :: num_points integer, intent(in) :: nelev real(r8), intent(in) :: gradient ! ! !LOCAL VARIABLES: + real(r8), allocatable :: gradients(:,:) + integer :: pt, ec + + character(len=*), parameter :: subname = 'vgc_specified_ec_times_ptSquared' + !----------------------------------------------------------------------- + + allocate(gradients(num_points, nelev)) + + do ec = 1, nelev + do pt = 1, num_points + gradients(pt, ec) = gradient * ec * pt**2 + end do + end do + + calculator = vertical_gradient_calculator_specified_type(gradients) + + end function vgc_specified_ec_times_ptSquared + + + !----------------------------------------------------------------------- + function constructor(gradients) result(this) + ! + ! !DESCRIPTION: + ! Create a new vertical_gradient_calculator_specified_type object. + ! + ! !USES: + ! + ! !ARGUMENTS: + type(vertical_gradient_calculator_specified_type) :: this ! function result + real(r8), intent(in) :: gradients(:,:) ! [pt, ec] + ! + ! !LOCAL VARIABLES: character(len=*), parameter :: subname = 'constructor' !----------------------------------------------------------------------- this%calculated = .false. - this%num_points = num_points - this%nelev = nelev - this%gradient = gradient + this%num_points = size(gradients, 1) + this%nelev = size(gradients, 2) - allocate(this%vertical_gradient(num_points, nelev)) - this%vertical_gradient(:,:) = nan + allocate(this%vertical_gradient(this%num_points, this%nelev)) + this%vertical_gradient(:,:) = gradients(:,:) end function constructor @@ -82,21 +118,16 @@ subroutine calc_gradients(this) ! !USES: ! ! !ARGUMENTS: - class(vertical_gradient_calculator_constant_type), intent(inout) :: this + class(vertical_gradient_calculator_specified_type), intent(inout) :: this ! ! !LOCAL VARIABLES: - integer :: pt, ec character(len=*), parameter :: subname = 'calc_gradients' !----------------------------------------------------------------------- SHR_ASSERT(.not. this%calculated, 'gradients already calculated') - do ec = 1, this%nelev - do pt = 1, this%num_points - this%vertical_gradient(pt, ec) = this%gradient * ec * pt**2 - end do - end do + ! Nothing to do in this stub this%calculated = .true. @@ -112,7 +143,7 @@ subroutine get_gradients_one_class(this, elevation_class, gradients) ! !USES: ! ! !ARGUMENTS: - class(vertical_gradient_calculator_constant_type), intent(in) :: this + class(vertical_gradient_calculator_specified_type), intent(in) :: this integer, intent(in) :: elevation_class ! gradients should already be allocated to the appropriate size @@ -131,4 +162,4 @@ subroutine get_gradients_one_class(this, elevation_class, gradients) gradients(:) = this%vertical_gradient(:, elevation_class) end subroutine get_gradients_one_class -end module vertical_gradient_calculator_constant +end module vertical_gradient_calculator_specified From eb6149f730423c6f41850893515f7f7db17e0960 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Sat, 30 Apr 2016 04:42:20 -0600 Subject: [PATCH 42/61] Specify initial guess and limit gradients These are two separate but related changes to vertical_gradient_calculator_continuous: (1) Specify initial guesses based on some other calculator (rather than mean gradient) (2) Put in place gradient limiting Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...vertical_gradient_calculator_2nd_order.F90 | 31 ++ .../vertical_gradient_calculator_base.F90 | 13 + ...ertical_gradient_calculator_continuous.F90 | 217 +++++++---- .../vertical_gradient_calculator_factory.F90 | 7 +- .../vertical_gradient_calculator_constant.F90 | 73 +++- .../CMakeLists.txt | 2 +- ..._vertical_gradient_calculator_2nd_order.pf | 1 - ...vertical_gradient_calculator_continuous.pf | 362 +++++++++++++++--- ...st_vertical_gradient_calculator_factory.pf | 1 - ..._gradient_calculator_continuousNoLimit.F90 | 55 +++ .../vertical_gradient_test_utils.F90 | 35 -- 11 files changed, 648 insertions(+), 149 deletions(-) create mode 100644 driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_calculator_continuousNoLimit.F90 delete mode 100644 driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index a8149d22c4e..e38015eec77 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -49,6 +49,7 @@ module vertical_gradient_calculator_2nd_order contains procedure :: calc_gradients procedure :: get_gradients_one_class + procedure :: get_gradients_one_point procedure, private :: check_topo ! check topographic heights procedure, private :: limit_gradient @@ -238,6 +239,36 @@ subroutine get_gradients_one_class(this, elevation_class, gradients) end subroutine get_gradients_one_class + !----------------------------------------------------------------------- + subroutine get_gradients_one_point(this, point, gradients) + ! + ! !DESCRIPTION: + ! Returns the vertical gradient for all elevation classes, for one point + ! + ! this%calc_gradients should already have been called + ! + ! !USES: + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_2nd_order_type), intent(in) :: this + integer, intent(in) :: point + + ! gradients should already be allocated to the appropriate size + real(r8), intent(out) :: gradients(:) + ! + ! !LOCAL VARIABLES: + + character(len=*), parameter :: subname = 'get_gradients_one_point' + !----------------------------------------------------------------------- + + SHR_ASSERT(this%calculated, errMsg(__FILE__, __LINE__)) + SHR_ASSERT(point <= this%num_points, errMsg(__FILE__, __LINE__)) + SHR_ASSERT((size(gradients) == this%nelev), errMsg(__FILE__, __LINE__)) + + gradients(:) = this%vertical_gradient(point, :) + + end subroutine get_gradients_one_point + !----------------------------------------------------------------------- subroutine check_topo(this) ! diff --git a/driver_cpl/driver/vertical_gradient_calculator_base.F90 b/driver_cpl/driver/vertical_gradient_calculator_base.F90 index fef5a3085a0..7c282bd25d8 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_base.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_base.F90 @@ -30,6 +30,9 @@ module vertical_gradient_calculator_base ! Get the vertical gradients for all points for a single elevation class procedure(get_gradients_one_class_interface), deferred :: get_gradients_one_class + ! Get the vertical gradients for all elevation classes for a single point + procedure(get_gradients_one_point_interface), deferred :: get_gradients_one_point + ! These routines are utility methods for derived classes; they should not be called ! by clients of this class. procedure, nopass :: check_elevclass_bounds_monotonic_increasing @@ -52,6 +55,16 @@ subroutine get_gradients_one_class_interface(this, elevation_class, gradients) ! vertical_gradient should already be allocated to the appropriate size real(r8), intent(out) :: gradients(:) end subroutine get_gradients_one_class_interface + + subroutine get_gradients_one_point_interface(this, point, gradients) + import :: vertical_gradient_calculator_base_type + import :: r8 + class(vertical_gradient_calculator_base_type), intent(in) :: this + integer, intent(in) :: point + + ! vertical_gradient should already be allocated to the appropriate size + real(r8), intent(out) :: gradients(:) + end subroutine get_gradients_one_point_interface end interface contains diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 index 29c7f06c3f0..0c9d804a5b3 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -30,7 +30,9 @@ module vertical_gradient_calculator_continuous real(r8), allocatable :: field(:,:) ! field(i,j) is elevation class i, point j real(r8), allocatable :: topo(:,:) ! topo(i,j) is elevation class i, point j - real(r8), allocatable :: vertical_gradient(:,:) ! precomputed vertical gradients; vertical_gradient(i,j) is elevation class i, field j + ! precomputed vertical gradients; vertical_gradient(i,j) is elevation class i, field + ! j + real(r8), allocatable :: vertical_gradient(:,:) logical, allocatable :: topo_valid(:) ! whether topo is valid in each point @@ -40,15 +42,28 @@ module vertical_gradient_calculator_continuous ! increasing. real(r8), allocatable :: elevclass_bounds(:) + ! Calculator to determine initial guesses for gradients. We determine how good the + ! solution is based on how well we match these initial guesses. We also fall back on + ! these initial guesses if the gradient in a given elevation class is determined to + ! be 'bad'. + class(vertical_gradient_calculator_base_type), allocatable :: calculator_initial_guess + logical :: calculated ! whether gradients have been calculated yet contains procedure :: calc_gradients procedure :: get_gradients_one_class + procedure :: get_gradients_one_point + + ! This is public so that it can be overridden and/or tested independently by unit + ! tests + procedure :: limit_gradients procedure, private :: check_topo ! check topographic heights procedure, private :: solve_for_vertical_gradients ! compute vertical gradients for all ECs, for points where we do a matrix solve + procedure, private :: dl ! lower half-width of ec + procedure, private :: du ! upper half-width of ec end type vertical_gradient_calculator_continuous_type interface vertical_gradient_calculator_continuous_type @@ -58,7 +73,8 @@ module vertical_gradient_calculator_continuous contains !----------------------------------------------------------------------- - function constructor(field, topo, elevclass_bounds) result(this) + function constructor(field, topo, elevclass_bounds, calculator_initial_guess) & + result(this) ! ! !DESCRIPTION: ! Creates a vertical_gradient_calculator_continuous_type object. @@ -83,6 +99,11 @@ function constructor(field, topo, elevclass_bounds) result(this) ! number of elevation classes, since it contains lower and upper bounds for each ! elevation class real(r8) , intent(in) :: elevclass_bounds(0:) + + ! Initial guesses for gradients. We determine how good the solution is based on how + ! well we match these initial guesses. We also fall back on these initial guesses if + ! the gradient in a given elevation class is determined to be 'bad'. + class(vertical_gradient_calculator_base_type), intent(in) :: calculator_initial_guess ! ! !LOCAL VARIABLES: @@ -111,6 +132,8 @@ function constructor(field, topo, elevclass_bounds) result(this) allocate(this%vertical_gradient(this%nelev, this%num_points)) this%vertical_gradient(:,:) = nan + allocate(this%calculator_initial_guess, source = calculator_initial_guess) + end function constructor !----------------------------------------------------------------------- @@ -134,6 +157,7 @@ subroutine calc_gradients(this) return end if + call this%calculator_initial_guess%calc_gradients() do pt = 1, this%num_points if (.not. this%topo_valid(pt)) then this%vertical_gradient(:,pt) = 0._r8 @@ -183,61 +207,36 @@ subroutine get_gradients_one_class(this, elevation_class, gradients) end subroutine get_gradients_one_class - !----------------------------------------------------------------------- - subroutine set_data_from_attr_vect(this, attr_vect, fieldname, toponame, elevclass_names) + subroutine get_gradients_one_point(this, point, gradients) ! ! !DESCRIPTION: - ! Extract data from an attribute vector. + ! Returns the vertical gradient for all elevation classes, for one point ! - ! Sets this%num_points, and allocates and sets this%field and this%topo. - ! - ! TODO(wjs, 2016-04-26) The current flow is that the constructor calls this - ! routine. It could be better to move this routine into a factory class that creates - ! objects by (1) calling this routine to extract fields from the attribute vector, and - ! then (2) calling the constructor of this class using these extracted data (so the - ! constructor would never need to be passed an attribute vector). + ! this%calc_gradients should already have been called ! ! !USES: - use mct_mod ! ! !ARGUMENTS: - class(vertical_gradient_calculator_continuous_type), intent(inout) :: this - type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data - character(len=*) , intent(in) :: fieldname ! base name of the field of interest - character(len=*) , intent(in) :: toponame ! base name of the topographic field - character(len=*) , intent(in) :: elevclass_names(:) ! strings corresponding to each elevation class + class(vertical_gradient_calculator_continuous_type), intent(in) :: this + integer, intent(in) :: point + + ! gradients should already be allocated to the appropriate size + real(r8), intent(out) :: gradients(:) ! ! !LOCAL VARIABLES: - integer :: elevclass - character(len=:), allocatable :: fieldname_ec - character(len=:), allocatable :: toponame_ec - ! The following temporary array is needed because mct wants pointers - real(r8), pointer :: temp(:) - - character(len=*), parameter :: subname = 'set_data_from_attr_vect' + character(len=*), parameter :: subname = 'get_gradients_one_point' !----------------------------------------------------------------------- - this%num_points = mct_aVect_lsize(attr_vect) + SHR_ASSERT(this%calculated, errMsg(__FILE__, __LINE__)) + SHR_ASSERT(point <= this%num_points, errMsg(__FILE__, __LINE__)) + SHR_ASSERT((size(gradients) == this%nelev), errMsg(__FILE__, __LINE__)) - allocate(this%field(this%nelev, this%num_points)) - allocate(this%topo(this%nelev, this%num_points)) - allocate(temp(this%num_points)) - - do elevclass = 1, this%nelev - fieldname_ec = trim(fieldname) // trim(elevclass_names(elevclass)) - call mct_aVect_exportRattr(attr_vect, fieldname_ec, temp) - this%field(elevclass,:) = temp(:) - - toponame_ec = trim(toponame) // trim(elevclass_names(elevclass)) - call mct_aVect_exportRattr(attr_vect, toponame_ec, temp) - this%topo(elevclass,:) = temp(:) - end do + gradients(:) = this%vertical_gradient(:, point) + + end subroutine get_gradients_one_point - deallocate(temp) - - end subroutine set_data_from_attr_vect !----------------------------------------------------------------------- subroutine check_topo(this) @@ -310,14 +309,12 @@ subroutine solve_for_vertical_gradients(this, pt) real(r8) :: field(this%nelev) ! mean field value of each elevation class real(r8) :: topo(this%nelev) ! mean topo of each elevation class real(r8) :: grad(this%nelev) ! computed gradient + real(r8) :: grad_initial_guess(this%nelev) ! initial guess for gradient real(r8) :: topo_interface(0:this%nelev) ! elevations at interfaces between classes - real(r8) :: dl(this%nelev) ! lower 1/2 widths of elevation classes - real(r8) :: du(this%nelev) ! upper 1/2 widths of elevation classes real(r8) :: h_lo(this%nelev) ! lower bounds for computing norms real(r8) :: h_hi(this%nelev) ! upper bounds for computing norms - real(r8) :: dgrad(this%nelev) ! grad - grad_mean + real(r8) :: dgrad(this%nelev) ! grad - grad_initial_guess real(r8) :: weight_grad(this%nelev) ! weight for dgrad in solution - real(r8) :: grad_mean ! mean value of gradient real(r8) :: diag(this%nelev-1) ! diagonal of tridiagonal matrix real(r8) :: subd(this%nelev-1) ! subdiagonal of tridiagonal matrix @@ -349,11 +346,6 @@ subroutine solve_for_vertical_gradients(this, pt) n = this%nelev - do i = 1, n - dl(i) = topo(i) - topo_interface(i-1) ! dl(1) is never used - du(i) = topo_interface(i) - topo(i) ! du(n) is never used - end do - ! FIXME(wjs, 2016-04-26) Extract method for the following two loops: returns ! weight_grad in each elevation class do i = 1, n @@ -385,6 +377,8 @@ subroutine solve_for_vertical_gradients(this, pt) end do + call this%calculator_initial_guess%get_gradients_one_point(pt, grad_initial_guess) + !-------------------------------------------------------------------- ! Set up matrix problem for gradient solution. ! The idea is to match field values at interfaces. @@ -407,7 +401,7 @@ subroutine solve_for_vertical_gradients(this, pt) ! So we add an additional constraint: ! Minimize the norm of the difference between the gradient in each class and the ! mean gradient, weighted by the range of the elevation class. - ! That is, minimize the sum over i of (wt(i) * (grad(i) - grad_mean))^2. + ! That is, minimize the sum over i of (wt(i) * (grad(i) - grad_initial_guess(i)))^2. ! ! The mean gradient is given by ! @@ -447,8 +441,8 @@ subroutine solve_for_vertical_gradients(this, pt) A(:,:) = 0._r8 do i = 1, n-1 - A(i,i) = du(i) / weight_grad(i) - A(i,i+1) = dl(i+1) / weight_grad(i+1) + A(i,i) = this%du(pt,i) / weight_grad(i) + A(i,i+1) = this%dl(pt,i+1) / weight_grad(i+1) end do ! Compute A * A^T, a tridiagonal matrix of size (n-1) @@ -485,13 +479,9 @@ subroutine solve_for_vertical_gradients(this, pt) b(i) = field(i+1) - field(i) enddo - ! Compute mean gradient + ! Subtract (A * weight_grad*grad_initial_guess), a vector of size(n-1) - grad_mean = (field(n) - field(1)) / (topo(n) - topo(1)) - - ! Subtract (A * weight_grad*grad_mean), a vector of size(n-1) - - b(:) = b(:) - matmul(A, weight_grad(:)*grad_mean) + b(:) = b(:) - matmul(A, weight_grad(:)*grad_initial_guess(:)) ! Multiply AT_Tinv by b to get the least-norm solution ! b has size (n-1), x_least_norm has size n @@ -501,18 +491,115 @@ subroutine solve_for_vertical_gradients(this, pt) ! Divide by the weighting factor to get dgrad dgrad(:) = x_least_norm(:) / weight_grad(:) - ! Add dgrad to the mean to get the total gradient - grad(:) = grad_mean + dgrad(:) + ! Add dgrad to the target gradient to get the total gradient + grad(:) = grad_initial_guess(:) + dgrad(:) ! Limit gradients - - ! FIXME(wjs, 2016-04-28) Extract this into a subroutine, or into a separate class - + call this%limit_gradients( & + pt = pt, & + grad_initial_guess = grad_initial_guess, & + grad = grad) ! Finally, set class-level values this%vertical_gradient(:,pt) = grad(:) end subroutine solve_for_vertical_gradients + !----------------------------------------------------------------------- + subroutine limit_gradients(this, pt, grad_initial_guess, grad) + ! + ! !DESCRIPTION: + ! Limit the computed gradients for the given point + ! + ! !USES: + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_continuous_type), intent(in) :: this + integer, intent(in) :: pt + + ! All of these arguments should have size this%nelev + + ! we'll back off to these initial guesses if there is a problem with grad in a given + ! elevation class + real(r8), intent(in) :: grad_initial_guess(:) + + ! upon input, grad contains the current gradient estimates; upon output, it is + ! modified to be limited + real(r8), intent(inout) :: grad(:) + ! + ! !LOCAL VARIABLES: + real(r8) :: field(this%nelev) ! mean field value of each elevation class + integer :: ec + real(r8) :: diff_max + real(r8) :: diff_min + real(r8) :: deviation_low + real(r8) :: deviation_high + real(r8) :: deviation_max + real(r8) :: deviation_min + + character(len=*), parameter :: subname = 'limit_gradients' + !----------------------------------------------------------------------- + + SHR_ASSERT_ALL((ubound(grad_initial_guess) == (/this%nelev/)), errMsg(__FILE__, __LINE__)) + SHR_ASSERT_ALL((ubound(grad) == (/this%nelev/)), errMsg(__FILE__, __LINE__)) + + field(:) = this%field(:,pt) + + ! Set gradient to 0 in lowest and highest elevation class + grad(1) = 0._r8 + grad(this%nelev) = 0._r8 + + do ec = 2, this%nelev - 1 + diff_max = max(field(ec+1), field(ec), field(ec-1)) - field(ec) + diff_min = min(field(ec+1), field(ec), field(ec-1)) - field(ec) + + ! Compute the max and min values of the deviation of the field from its mean value. + deviation_low = -this%dl(pt,ec) * grad(ec) + deviation_high = this%du(pt,ec) * grad(ec) + deviation_max = max(deviation_high, deviation_low) + deviation_min = min(deviation_high, deviation_low) + + if (deviation_max > diff_max .or. deviation_min < diff_min) then + grad(ec) = grad_initial_guess(ec) + end if + end do + + end subroutine limit_gradients + + !----------------------------------------------------------------------- + function dl(this, pt, ec) + ! + ! !DESCRIPTION: + ! Return lower half-width of elevation class ec in point pt + ! + ! !ARGUMENTS: + real(r8) :: dl ! function result + class(vertical_gradient_calculator_continuous_type), intent(in) :: this + integer, intent(in) :: pt + integer, intent(in) :: ec + !----------------------------------------------------------------------- + + dl = this%topo(ec, pt) - this%elevclass_bounds(ec - 1) + + end function dl + + + !----------------------------------------------------------------------- + function du(this, pt, ec) + ! + ! !DESCRIPTION: + ! Return upper half-width of elevation class ec in point pt + ! + ! !ARGUMENTS: + real(r8) :: du ! function result + class(vertical_gradient_calculator_continuous_type), intent(in) :: this + integer, intent(in) :: pt + integer, intent(in) :: ec + !----------------------------------------------------------------------- + + du = this%elevclass_bounds(ec) - this%topo(ec, pt) + + end function du + end module vertical_gradient_calculator_continuous diff --git a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 index 3e9244d4aa8..7fcba603b6a 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 @@ -94,6 +94,7 @@ function create_vertical_gradient_calculator_continuous( & integer :: nelev real(r8), allocatable :: field(:,:) real(r8), allocatable :: topo(:,:) + type(vertical_gradient_calculator_2nd_order_type) :: calculator_initial_guess character(len=*), parameter :: subname = 'create_vertical_gradient_calculator_continuous' !----------------------------------------------------------------------- @@ -104,9 +105,13 @@ function create_vertical_gradient_calculator_continuous( & call extract_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names, & field, topo) - calculator = vertical_gradient_calculator_continuous_type( & + calculator_initial_guess = vertical_gradient_calculator_2nd_order_type( & field = field, topo = topo, elevclass_bounds = elevclass_bounds) + calculator = vertical_gradient_calculator_continuous_type( & + field = field, topo = topo, elevclass_bounds = elevclass_bounds, & + calculator_initial_guess = calculator_initial_guess) + end function create_vertical_gradient_calculator_continuous !----------------------------------------------------------------------- diff --git a/driver_cpl/unit_test/stubs/vertical_gradient_calculator_constant.F90 b/driver_cpl/unit_test/stubs/vertical_gradient_calculator_constant.F90 index de5be877d2b..99c2529c1a6 100644 --- a/driver_cpl/unit_test/stubs/vertical_gradient_calculator_constant.F90 +++ b/driver_cpl/unit_test/stubs/vertical_gradient_calculator_constant.F90 @@ -34,6 +34,7 @@ module vertical_gradient_calculator_specified contains procedure :: calc_gradients procedure :: get_gradients_one_class + procedure :: get_gradients_one_point end type vertical_gradient_calculator_specified_type interface vertical_gradient_calculator_specified_type @@ -42,6 +43,10 @@ module vertical_gradient_calculator_specified ! Creates a calculator where the gradient in ec i, pt j is gradient * i * j^2 public :: vgc_specified_ec_times_ptSquared + + ! Creates a calculator where the gradient is constant for each point, set as the mean + ! slope from the lowest to highest elev class + public :: vgc_specified_mean_slope contains !----------------------------------------------------------------------- @@ -82,6 +87,44 @@ function vgc_specified_ec_times_ptSquared(num_points, nelev, gradient) & end function vgc_specified_ec_times_ptSquared + !----------------------------------------------------------------------- + function vgc_specified_mean_slope(data, topo) result(calculator) + ! + ! !DESCRIPTION: + ! Creates a calculator where the gradient is constant for all elevation classes - + ! though can differ for each point. Specifically, it is set to the mean slope from + ! the lowest to highest elev class + ! + ! !USES: + ! + ! !ARGUMENTS: + type(vertical_gradient_calculator_specified_type) :: calculator ! function result + real(r8), intent(in) :: data(:,:) ! [pt, ec] + real(r8), intent(in) :: topo(:,:) ! [pt, ec] + ! + ! !LOCAL VARIABLES: + integer :: num_points + integer :: nelev + real(r8), allocatable :: gradients(:,:) + integer pt + + character(len=*), parameter :: subname = 'vgc_specified_mean_slope' + !----------------------------------------------------------------------- + + num_points = size(data,1) + nelev = size(data,2) + SHR_ASSERT_ALL((ubound(topo) == (/num_points, nelev/)), 'bad size for topo') + + allocate(gradients(num_points, nelev)) + + do pt = 1, num_points + gradients(pt, :) = (data(pt,nelev) - data(pt,1)) / & + (topo(pt,nelev) - topo(pt,1)) + end do + + calculator = vertical_gradient_calculator_specified_type(gradients) + + end function vgc_specified_mean_slope !----------------------------------------------------------------------- function constructor(gradients) result(this) @@ -138,7 +181,7 @@ end subroutine calc_gradients subroutine get_gradients_one_class(this, elevation_class, gradients) ! ! !DESCRIPTION: - ! Calculate the vertical gradient for all points + ! Return the vertical gradients for one elevation class, for all points ! ! !USES: ! @@ -150,7 +193,6 @@ subroutine get_gradients_one_class(this, elevation_class, gradients) real(r8), intent(out) :: gradients(:) ! ! !LOCAL VARIABLES: - integer :: grid_cell character(len=*), parameter :: subname = 'get_gradients_one_class' !----------------------------------------------------------------------- @@ -162,4 +204,31 @@ subroutine get_gradients_one_class(this, elevation_class, gradients) gradients(:) = this%vertical_gradient(:, elevation_class) end subroutine get_gradients_one_class + !----------------------------------------------------------------------- + subroutine get_gradients_one_point(this, point, gradients) + ! + ! !DESCRIPTION: + ! Return the vertical gradient for all elevation classes, for one point + ! + ! !USES: + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_specified_type), intent(in) :: this + integer, intent(in) :: point + + ! gradients should already be allocated to the appropriate size + real(r8), intent(out) :: gradients(:) + ! + ! !LOCAL VARIABLES: + + character(len=*), parameter :: subname = 'get_gradients_one_class' + !----------------------------------------------------------------------- + + SHR_ASSERT(this%calculated, 'gradients not yet calculated') + SHR_ASSERT(point <= this%num_points, subname//': elevation class exceeds bounds') + SHR_ASSERT((size(gradients) == this%nelev), subname//': wrong size for vertical gradient') + + gradients(:) = this%vertical_gradient(point, :) + end subroutine get_gradients_one_point + end module vertical_gradient_calculator_specified diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt index d46d0c11bf3..ffe842381a1 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt @@ -5,7 +5,7 @@ set (pfunit_sources ) set (extra_sources - vertical_gradient_test_utils.F90 + vertical_gradient_calculator_continuousNoLimit.F90 ) create_pFUnit_test(vertical_gradient_calculator vertical_gradient_calculator_exe diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf index 6c8555c5a00..f27e8faf757 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_2nd_order.pf @@ -6,7 +6,6 @@ module test_vertical_gradient_calculator_2nd_order use vertical_gradient_calculator_base use vertical_gradient_calculator_2nd_order use shr_kind_mod, only : r8 => shr_kind_r8 - use vertical_gradient_test_utils implicit none diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf index f865b11f3fd..5a8e7129b6b 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -4,8 +4,9 @@ module test_vertical_gradient_calculator_continuous use pfunit_mod use vertical_gradient_calculator_continuous + use vertical_gradient_calculator_continuousNoLimit + use vertical_gradient_calculator_specified use shr_kind_mod , only : r8 => shr_kind_r8 - use vertical_gradient_test_utils implicit none save @@ -17,6 +18,8 @@ module test_vertical_gradient_calculator_continuous procedure :: tearDown procedure :: create_calculator procedure :: create_calculator_one_point + procedure :: create_calculatorNoLimit + procedure :: create_calculatorNoLimit_one_point procedure :: write_output end type TestVertGradCalcCont @@ -60,7 +63,8 @@ contains calculator = vertical_gradient_calculator_continuous_type( & field = data, & topo = topo, & - elevclass_bounds = elevclass_bounds) + elevclass_bounds = elevclass_bounds, & + calculator_initial_guess = vgc_specified_mean_slope(data, topo)) call calculator%calc_gradients() end function create_calculator @@ -85,6 +89,53 @@ contains elevclass_bounds = elevclass_bounds) end function create_calculator_one_point + function create_calculatorNoLimit(this, topo, data, elevclass_bounds) & + result(calculator) + type(vgc_continuousNoLimit_type) :: calculator + class(TestVertGradCalcCont), intent(inout) :: this + real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j + real(r8), intent(in) :: data(:,:) ! data(i,j) is point i, elevation class j + + ! bounds of each elevation class; this array should have one more element than the + ! number of elevation classes, since it contains lower and upper bounds for each + ! elevation class + real(r8), intent(in) :: elevclass_bounds(:) + + integer :: n_elev_classes + + n_elev_classes = size(data,2) + @assertEqual(size(data), size(topo)) + @assertEqual(n_elev_classes + 1, size(elevclass_bounds)) + + calculator = vgc_continuousNoLimit_type( & + field = data, & + topo = topo, & + elevclass_bounds = elevclass_bounds, & + calculator_initial_guess = vgc_specified_mean_slope(data, topo)) + call calculator%calc_gradients() + + end function create_calculatorNoLimit + + function create_calculatorNoLimit_one_point(this, topo, data, elevclass_bounds) & + result(calculator) + ! Convenience wrapper to create_calculator, when just dealing with one point + type(vgc_continuousNoLimit_type) :: calculator + class(TestVertGradCalcCont), intent(inout) :: this + real(r8), intent(in) :: topo(:) + real(r8), intent(in) :: data(:) + + ! bounds of each elevation class; this array should have one more element than the + ! number of elevation classes, since it contains lower and upper bounds for each + ! elevation class + real(r8), intent(in) :: elevclass_bounds(:) + + + calculator = this%create_calculatorNoLimit( & + topo = reshape(topo, [1, size(topo)]), & + data = reshape(data, [1, size(data)]), & + elevclass_bounds = elevclass_bounds) + end function create_calculatorNoLimit_one_point + subroutine write_output(this, elevclass_bounds, topo, data, gradients) class(TestVertGradCalcCont), intent(inout) :: this real(r8), intent(in) :: elevclass_bounds(:) @@ -125,12 +176,10 @@ contains calculator = this%create_calculator_one_point(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - gradients = all_gradients_one_point(calculator, & - n_elev_classes = 5, & - npts = 1, & - pt = 1) + call calculator%get_gradients_one_point(point = 1, gradients = gradients) - expected_gradients(:) = [0.2_r8, 0.15_r8, 0.10_r8, 0.05_r8, 0.0_r8] + ! Note that gradient is set to 0 in lowest EC + expected_gradients(:) = [0._r8, 0.15_r8, 0.10_r8, 0.05_r8, 0.0_r8] @assertEqual(expected_gradients, gradients, tolerance=tol) ! FIXME(wjs, 2016-04-27) Move this to somewhere else @@ -142,6 +191,35 @@ contains end subroutine basic + @Test + subroutine basic_noLimit(this) + ! Ensure that gradients are computed as expected before applying the limiter + class(TestVertGradCalcCont), intent(inout) :: this + type(vgc_continuousNoLimit_type) :: calculator + real(r8), parameter :: elevclass_bounds(6) = & + [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8] + real(r8), parameter :: topo(5) = [10._r8, 30._r8, 50._r8, 70._r8, 90._r8] + real(r8), parameter :: data(5) = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8] + real(r8) :: gradients(5) + real(r8) :: expected_gradients(5) + + calculator = this%create_calculatorNoLimit_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + call calculator%get_gradients_one_point(point = 1, gradients = gradients) + + expected_gradients(:) = [0.2_r8, 0.15_r8, 0.10_r8, 0.05_r8, 0.0_r8] + @assertEqual(expected_gradients, gradients, tolerance=tol) + + ! FIXME(wjs, 2016-04-27) Move this to somewhere else + call this%write_output( & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + gradients = gradients) + + end subroutine basic_noLimit + ! FIXME(wjs, 2016-04-27) delete this @Test subroutine real_temp(this) @@ -183,10 +261,7 @@ contains calculator = this%create_calculator_one_point(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - gradients = all_gradients_one_point(calculator, & - n_elev_classes = 10, & - npts = 1, & - pt = 1) + call calculator%get_gradients_one_point(point = 1, gradients = gradients) call this%write_output( & elevclass_bounds = elevclass_bounds, & @@ -218,10 +293,7 @@ contains calculator = this%create_calculator_one_point(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - gradients = all_gradients_one_point(calculator, & - n_elev_classes = 10, & - npts = 1, & - pt = 1) + call calculator%get_gradients_one_point(point = 1, gradients = gradients) call this%write_output( & elevclass_bounds = elevclass_bounds, & @@ -255,10 +327,7 @@ contains calculator = this%create_calculator_one_point(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - gradients = all_gradients_one_point(calculator, & - n_elev_classes = 6, & - npts = 1, & - pt = 1) + call calculator%get_gradients_one_point(point = 1, gradients = gradients) call this%write_output( & elevclass_bounds = elevclass_bounds, & @@ -290,10 +359,7 @@ contains calculator = this%create_calculator_one_point(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - gradients = all_gradients_one_point(calculator, & - n_elev_classes = 10, & - npts = 1, & - pt = 1) + call calculator%get_gradients_one_point(point = 1, gradients = gradients) call this%write_output( & elevclass_bounds = elevclass_bounds, & @@ -306,42 +372,252 @@ contains subroutine topo_outOfBoundsHigh(this) class(TestVertGradCalcCont), intent(inout) :: this type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(4) = & - [0._r8, 20._r8, 40._r8, 60._r8] - real(r8), parameter :: topo(3) = [10._r8, 40._r8 + 1.e-5_r8, 50._r8] - real(r8), parameter :: data(3) = [2._r8, 5.5_r8, 8._r8] - real(r8) :: gradients(3) + real(r8), parameter :: elevclass_bounds(6) = & + [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8] + real(r8), parameter :: topo(5) = [10._r8, 40._r8 + 1.e-5_r8, 50._r8, 70._r8, 90._r8] + real(r8), parameter :: data(5) = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8] + real(r8) :: gradients(5) calculator = this%create_calculator_one_point(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - gradients = all_gradients_one_point(calculator, & - n_elev_classes = 3, & - npts = 1, & - pt = 1) + call calculator%get_gradients_one_point(point = 1, gradients = gradients) - @assertEqual([0._r8, 0._r8, 0._r8], gradients) + @assertEqual([0._r8, 0._r8, 0._r8, 0._r8, 0._r8], gradients) end subroutine topo_outOfBoundsHigh @Test subroutine topo_outOfBoundsLow(this) class(TestVertGradCalcCont), intent(inout) :: this type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(4) = & - [0._r8, 20._r8, 40._r8, 60._r8] - real(r8), parameter :: topo(3) = [10._r8, 20._r8 - 1.e-5_r8, 50._r8] - real(r8), parameter :: data(3) = [2._r8, 5.5_r8, 8._r8] - real(r8) :: gradients(3) + real(r8), parameter :: elevclass_bounds(6) = & + [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8] + real(r8), parameter :: topo(5) = [10._r8, 20._r8 - 1.e-5_r8, 50._r8, 70._r8, 90._r8] + real(r8), parameter :: data(5) = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8] + real(r8) :: gradients(5) calculator = this%create_calculator_one_point(topo=topo, data=data, & elevclass_bounds=elevclass_bounds) - gradients = all_gradients_one_point(calculator, & - n_elev_classes = 3, & - npts = 1, & - pt = 1) + call calculator%get_gradients_one_point(point = 1, gradients = gradients) - @assertEqual([0._r8, 0._r8, 0._r8], gradients) + @assertEqual([0._r8, 0._r8, 0._r8, 0._r8, 0._r8], gradients) end subroutine topo_outOfBoundsLow + ! ------------------------------------------------------------------------ + ! Tests of limit_gradients + ! + ! For many of the tests above, we stubbed out limit_gradients, so we test that routine + ! separately here. + ! ------------------------------------------------------------------------ + + @Test + subroutine limitGradients_0InFirstEC(this) + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + real(r8), parameter :: elevclass_bounds(6) = & + [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8] + real(r8), parameter :: topo(5) = [10._r8, 30._r8, 50._r8, 70._r8, 90._r8] + real(r8), parameter :: data(5) = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8] + real(r8) :: grad(5) = [1._r8, 2._r8, 3._r8, 4._r8, 5._r8] + real(r8) :: grad_initial_guess(5) + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + grad_initial_guess = grad + call calculator%limit_gradients(1, grad_initial_guess, grad) + + @assertEqual(0._r8, grad(1)) + end subroutine limitGradients_0InFirstEC + + @Test + subroutine limitGradients_0InLastEC(this) + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + real(r8), parameter :: elevclass_bounds(6) = & + [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8] + real(r8), parameter :: topo(5) = [10._r8, 30._r8, 50._r8, 70._r8, 90._r8] + real(r8), parameter :: data(5) = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8] + real(r8) :: grad(5) = [1._r8, 2._r8, 3._r8, 4._r8, 5._r8] + real(r8) :: grad_initial_guess(5) + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + grad_initial_guess = grad + call calculator%limit_gradients(1, grad_initial_guess, grad) + + @assertEqual(0._r8, grad(5)) + end subroutine limitGradients_0InLastEC + + @Test + subroutine limitGradients_almostLimitedPositiveLB(this) + ! Make sure that a positive gradient that should *almost* (but not quite) be limited + ! by the limiter (due to the lower bound) isn't limited. + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [11._r8, 12._r8, 100._r8] + real(r8), parameter :: grad_ec2 = 0.039999_r8 + real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] + real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + call calculator%limit_gradients(1, grad_initial_guess, grad) + + @assertEqual(grad_ec2, grad(2)) + end subroutine limitGradients_almostLimitedPositiveLB + + @Test + subroutine limitGradients_almostLimitedPositiveUB(this) + ! Make sure that a positive gradient that should *almost* (but not quite) be limited + ! by the limiter (due to the upper bound) isn't limited. + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [0._r8, 12._r8, 13._r8] + real(r8), parameter :: grad_ec2 = 0.013332_r8 + real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] + real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + call calculator%limit_gradients(1, grad_initial_guess, grad) + + @assertEqual(grad_ec2, grad(2)) + end subroutine limitGradients_almostLimitedPositiveUB + + @Test + subroutine limitGradients_almostLimitedNegativeLB(this) + ! Make sure that a negative gradient that should *almost* (but not quite) be limited + ! by the limiter (due to the lower bound) isn't limited. + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [13._r8, 12._r8, 0._r8] + real(r8), parameter :: grad_ec2 = -0.039999_r8 + real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] + real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + call calculator%limit_gradients(1, grad_initial_guess, grad) + + @assertEqual(grad_ec2, grad(2)) + end subroutine limitGradients_almostLimitedNegativeLB + + @Test + subroutine limitGradients_almostLimitedNegativeUB(this) + ! Make sure that a negative gradient that should *almost* (but not quite) be limited + ! by the limiter (due to the upper bound) isn't limited. + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [100._r8, 12._r8, 11._r8] + real(r8), parameter :: grad_ec2 = -0.013332_r8 + real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] + real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + call calculator%limit_gradients(1, grad_initial_guess, grad) + + @assertEqual(grad_ec2, grad(2)) + end subroutine limitGradients_almostLimitedNegativeUB + + @Test + subroutine limitGradients_limitedPositiveLB(this) + ! Make sure that a positive gradient that should be limited by the limiter (due to the + ! lower bound) is in fact limited. + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [11._r8, 12._r8, 100._r8] + real(r8), parameter :: grad_ec2 = 0.04001_r8 + real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] + real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + call calculator%limit_gradients(1, grad_initial_guess, grad) + + @assertEqual(grad_initial_guess(2), grad(2)) + end subroutine limitGradients_limitedPositiveLB + + @Test + subroutine limitGradients_limitedPositiveUB(this) + ! Make sure that a positive gradient that should be limited by the limiter (due to the + ! upper bound) is in fact limited. + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [0._r8, 12._r8, 13._r8] + real(r8), parameter :: grad_ec2 = 0.013334_r8 + real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] + real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + call calculator%limit_gradients(1, grad_initial_guess, grad) + + @assertEqual(grad_initial_guess(2), grad(2)) + end subroutine limitGradients_limitedPositiveUB + + @Test + subroutine limitGradients_limitedNegativeLB(this) + ! Make sure that a negative gradient that should be limited by the limiter (due to the + ! lower bound) is in fact limited. + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [13._r8, 12._r8, 0._r8] + real(r8), parameter :: grad_ec2 = -0.04001_r8 + real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] + real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + call calculator%limit_gradients(1, grad_initial_guess, grad) + + @assertEqual(grad_initial_guess(2), grad(2)) + end subroutine limitGradients_limitedNegativeLB + + @Test + subroutine limitGradients_limitedNegativeUB(this) + ! Make sure that a negative gradient that should be limited by the limiter (due to the + ! upper bound) is in fact limited. + class(TestVertGradCalcCont), intent(inout) :: this + type(vertical_gradient_calculator_continuous_type) :: calculator + real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] + real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] + real(r8), parameter :: data(3) = [100._r8, 12._r8, 11._r8] + real(r8), parameter :: grad_ec2 = -0.013334_r8 + real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] + real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] + + calculator = this%create_calculator_one_point(topo=topo, data=data, & + elevclass_bounds=elevclass_bounds) + + call calculator%limit_gradients(1, grad_initial_guess, grad) + + @assertEqual(grad_initial_guess(2), grad(2)) + end subroutine limitGradients_limitedNegativeUB + + end module test_vertical_gradient_calculator_continuous diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_factory.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_factory.pf index e0bf4095e51..d72dd917cea 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_factory.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_factory.pf @@ -5,7 +5,6 @@ module test_vertical_gradient_calculator_factory use pfunit_mod use vertical_gradient_calculator_factory use shr_kind_mod , only : r8 => shr_kind_r8 - use vertical_gradient_test_utils use mct_mod, only : mct_aVect, mct_aVect_clean use mct_wrapper_mod, only : mct_init, mct_clean use avect_wrapper_mod diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_calculator_continuousNoLimit.F90 b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_calculator_continuousNoLimit.F90 new file mode 100644 index 00000000000..504e31b7b46 --- /dev/null +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_calculator_continuousNoLimit.F90 @@ -0,0 +1,55 @@ +module vertical_gradient_calculator_continuousNoLimit + ! This module provides a type that inherits from + ! vertical_gradient_calculator_continuous, overriding the limiting to result in no + ! limiting of the initially-computed gradients. + + use shr_kind_mod, only : r8 => shr_kind_r8 + use vertical_gradient_calculator_base, only : & + vertical_gradient_calculator_base_type + use vertical_gradient_calculator_continuous, only : & + vertical_gradient_calculator_continuous_type + + implicit none + private + + public :: vgc_continuousNoLimit_type + + type, extends(vertical_gradient_calculator_continuous_type) :: & + vgc_continuousNoLimit_type + private + + contains + procedure :: limit_gradients + end type vgc_continuousNoLimit_type + + interface vgc_continuousNoLimit_type + module procedure constructor + end interface vgc_continuousNoLimit_type + +contains + + function constructor(field, topo, elevclass_bounds, calculator_initial_guess) result(this) + type(vgc_continuousNoLimit_type) :: this + real(r8), intent(in) :: field(:,:) ! field(i,j) is point i, elevation class j + real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j + ! bounds of each elevation class; this array should have one more element than the + ! number of elevation classes, since it contains lower and upper bounds for each + ! elevation class + real(r8) , intent(in) :: elevclass_bounds(0:) + class(vertical_gradient_calculator_base_type), intent(in) :: calculator_initial_guess + + this%vertical_gradient_calculator_continuous_type = & + vertical_gradient_calculator_continuous_type( & + field, topo, elevclass_bounds, calculator_initial_guess) + end function constructor + + subroutine limit_gradients(this, pt, grad_initial_guess, grad) + class(vgc_continuousNoLimit_type), intent(in) :: this + integer, intent(in) :: pt + real(r8), intent(in) :: grad_initial_guess(:) + real(r8), intent(inout) :: grad(:) + + ! do nothing + end subroutine limit_gradients + +end module vertical_gradient_calculator_continuousNoLimit diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 deleted file mode 100644 index a01e15a4e15..00000000000 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_test_utils.F90 +++ /dev/null @@ -1,35 +0,0 @@ -module vertical_gradient_test_utils - - ! Utilities to aid testing of vertical gradient calculators - -#include "shr_assert.h" - use shr_log_mod, only : errMsg => shr_log_errMsg - use shr_kind_mod , only : r8 => shr_kind_r8 - use vertical_gradient_calculator_base, only : vertical_gradient_calculator_base_type - - implicit none - private - - public :: all_gradients_one_point ! Return gradients for all ECs for one point - -contains - - function all_gradients_one_point(calculator, n_elev_classes, npts, pt) result(gradients) - ! Return gradients for all ECs for one point - class(vertical_gradient_calculator_base_type), intent(in) :: calculator - integer, intent(in) :: n_elev_classes ! number of elevation classes in this calculator - integer, intent(in) :: npts ! number of points in this calculator - integer, intent(in) :: pt ! point of interest - real(r8) :: gradients(n_elev_classes) ! function result - - integer :: ec - real(r8) :: gradients_one_ec(npts) - - do ec = 1, n_elev_classes - call calculator%get_gradients_one_class(ec, gradients_one_ec) - gradients(ec) = gradients_one_ec(pt) - end do - end function all_gradients_one_point - -end module vertical_gradient_test_utils - From ad6bb83c0f85036d8e414eaf52aae3e41295a4b6 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Sat, 30 Apr 2016 04:55:30 -0600 Subject: [PATCH 43/61] Refactor limit_gradients Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...ertical_gradient_calculator_continuous.F90 | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 index 0c9d804a5b3..6cc39bf7156 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -530,12 +530,10 @@ subroutine limit_gradients(this, pt, grad_initial_guess, grad) ! !LOCAL VARIABLES: real(r8) :: field(this%nelev) ! mean field value of each elevation class integer :: ec - real(r8) :: diff_max - real(r8) :: diff_min - real(r8) :: deviation_low - real(r8) :: deviation_high - real(r8) :: deviation_max - real(r8) :: deviation_min + real(r8) :: val_at_lb ! value at the lower bound interface of an elevation class + real(r8) :: val_at_ub ! value at the upper bound interface of an elevation class + logical :: val_at_lb_outside_bounds + logical :: val_at_ub_outside_bounds character(len=*), parameter :: subname = 'limit_gradients' !----------------------------------------------------------------------- @@ -550,20 +548,30 @@ subroutine limit_gradients(this, pt, grad_initial_guess, grad) grad(this%nelev) = 0._r8 do ec = 2, this%nelev - 1 - diff_max = max(field(ec+1), field(ec), field(ec-1)) - field(ec) - diff_min = min(field(ec+1), field(ec), field(ec-1)) - field(ec) + val_at_lb = field(ec) - (this%dl(pt,ec) * grad(ec)) + val_at_lb_outside_bounds = is_outside_bounds(val_at_lb, field(ec), field(ec-1)) - ! Compute the max and min values of the deviation of the field from its mean value. - deviation_low = -this%dl(pt,ec) * grad(ec) - deviation_high = this%du(pt,ec) * grad(ec) - deviation_max = max(deviation_high, deviation_low) - deviation_min = min(deviation_high, deviation_low) + val_at_ub = field(ec) + (this%du(pt,ec) * grad(ec)) + val_at_ub_outside_bounds = is_outside_bounds(val_at_ub, field(ec), field(ec+1)) - if (deviation_max > diff_max .or. deviation_min < diff_min) then + if (val_at_lb_outside_bounds .or. val_at_ub_outside_bounds) then grad(ec) = grad_initial_guess(ec) end if end do + contains + pure logical function is_outside_bounds(val, bound1, bound2) + ! Returns true if val is outside the interval given by bound1 and bound2 + real(r8), intent(in) :: val, bound1, bound2 + + if (val < bound1 .and. val < bound2) then + is_outside_bounds = .true. + else if (val > bound1 .and. val > bound2) then + is_outside_bounds = .true. + else + is_outside_bounds = .false. + end if + end function is_outside_bounds end subroutine limit_gradients !----------------------------------------------------------------------- From 0ab8f0f7e099aa3b131d7ff998569b0f7b275824 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Sat, 30 Apr 2016 06:02:56 -0600 Subject: [PATCH 44/61] Print various statistics Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...ertical_gradient_calculator_continuous.F90 | 52 ++++++++++++++++++- ...vertical_gradient_calculator_continuous.pf | 1 - ..._gradient_calculator_continuousNoLimit.F90 | 2 +- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 index 6cc39bf7156..53304a09422 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -50,10 +50,16 @@ module vertical_gradient_calculator_continuous logical :: calculated ! whether gradients have been calculated yet + ! Various statistics for printing diagnostics. + logical, allocatable :: zeroed_from_topo_out_of_bounds(:) ! [num_points] + logical, allocatable :: limited_to_zero(:,:) ! [nelev, num_points] + logical, allocatable :: limited_to_initial_guess(:,:) ! [nelev, num_points] + contains procedure :: calc_gradients procedure :: get_gradients_one_class procedure :: get_gradients_one_point + procedure :: print_statistics ! This is public so that it can be overridden and/or tested independently by unit ! tests @@ -134,6 +140,13 @@ function constructor(field, topo, elevclass_bounds, calculator_initial_guess) & allocate(this%calculator_initial_guess, source = calculator_initial_guess) + allocate(this%zeroed_from_topo_out_of_bounds(this%num_points)) + this%zeroed_from_topo_out_of_bounds(:) = .false. + allocate(this%limited_to_zero(this%nelev, this%num_points)) + this%limited_to_zero(:,:) = .false. + allocate(this%limited_to_initial_guess(this%nelev, this%num_points)) + this%limited_to_initial_guess(:,:) = .false. + end function constructor !----------------------------------------------------------------------- @@ -161,6 +174,7 @@ subroutine calc_gradients(this) do pt = 1, this%num_points if (.not. this%topo_valid(pt)) then this%vertical_gradient(:,pt) = 0._r8 + this%zeroed_from_topo_out_of_bounds(pt) = .true. else call this%solve_for_vertical_gradients(pt) end if @@ -237,6 +251,39 @@ subroutine get_gradients_one_point(this, point, gradients) end subroutine get_gradients_one_point + !----------------------------------------------------------------------- + subroutine print_statistics(this) + ! + ! !DESCRIPTION: + ! Print various statistics on the solve to the logunit + ! + ! !USES: + ! + ! !ARGUMENTS: + class(vertical_gradient_calculator_continuous_type), intent(in) :: this + ! + ! !LOCAL VARIABLES: + integer :: ec + integer :: num_not_out_of_bounds + + character(len=*), parameter :: subname = 'print_statistics' + !----------------------------------------------------------------------- + + SHR_ASSERT(this%calculated, errMsg(__FILE__, __LINE__)) + + write(logunit, '(a)') "Vertical gradient calculator statistics: " + write(logunit, '(a, f10.6)') "Fraction with topo out of bounds: ", & + real(count(this%zeroed_from_topo_out_of_bounds), r8) / real(this%num_points, r8) + num_not_out_of_bounds = this%num_points - count(this%zeroed_from_topo_out_of_bounds) + do ec = 1, this%nelev + write(logunit, '(a, i4, f10.6)') "Remaining fraction limited to 0: ", ec, & + real(count(this%limited_to_zero(ec,:)), r8) / real(num_not_out_of_bounds, r8) + write(logunit, '(a, i4, f10.6)') "Remaining fraction limited to initial guess: ", ec, & + real(count(this%limited_to_initial_guess(ec,:)), r8) / real(num_not_out_of_bounds, r8) + end do + + end subroutine print_statistics + !----------------------------------------------------------------------- subroutine check_topo(this) @@ -514,7 +561,7 @@ subroutine limit_gradients(this, pt, grad_initial_guess, grad) ! !USES: ! ! !ARGUMENTS: - class(vertical_gradient_calculator_continuous_type), intent(in) :: this + class(vertical_gradient_calculator_continuous_type), intent(inout) :: this integer, intent(in) :: pt ! All of these arguments should have size this%nelev @@ -545,7 +592,9 @@ subroutine limit_gradients(this, pt, grad_initial_guess, grad) ! Set gradient to 0 in lowest and highest elevation class grad(1) = 0._r8 + this%limited_to_zero(1, pt) = .true. grad(this%nelev) = 0._r8 + this%limited_to_zero(this%nelev, pt) = .true. do ec = 2, this%nelev - 1 val_at_lb = field(ec) - (this%dl(pt,ec) * grad(ec)) @@ -556,6 +605,7 @@ subroutine limit_gradients(this, pt, grad_initial_guess, grad) if (val_at_lb_outside_bounds .or. val_at_ub_outside_bounds) then grad(ec) = grad_initial_guess(ec) + this%limited_to_initial_guess(ec, pt) = .true. end if end do diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf index 5a8e7129b6b..0cd9e78155a 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -619,5 +619,4 @@ contains @assertEqual(grad_initial_guess(2), grad(2)) end subroutine limitGradients_limitedNegativeUB - end module test_vertical_gradient_calculator_continuous diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_calculator_continuousNoLimit.F90 b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_calculator_continuousNoLimit.F90 index 504e31b7b46..7cbb4af20ea 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_calculator_continuousNoLimit.F90 +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_calculator_continuousNoLimit.F90 @@ -44,7 +44,7 @@ function constructor(field, topo, elevclass_bounds, calculator_initial_guess) re end function constructor subroutine limit_gradients(this, pt, grad_initial_guess, grad) - class(vgc_continuousNoLimit_type), intent(in) :: this + class(vgc_continuousNoLimit_type), intent(inout) :: this integer, intent(in) :: pt real(r8), intent(in) :: grad_initial_guess(:) real(r8), intent(inout) :: grad(:) From 14173df82ec291bcd99b63aa4c0d276eb3265650 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Sat, 30 Apr 2016 07:01:52 -0600 Subject: [PATCH 45/61] Use factory method, call print method Test suite: None Test baseline: N/A Test namelist changes: N/A Test status: N/A Fixes: None User interface changes?: No Code review: None --- driver_cpl/driver/prep_glc_mod.F90 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/driver_cpl/driver/prep_glc_mod.F90 b/driver_cpl/driver/prep_glc_mod.F90 index 7e9d425e9aa..496261d90b7 100644 --- a/driver_cpl/driver/prep_glc_mod.F90 +++ b/driver_cpl/driver/prep_glc_mod.F90 @@ -407,6 +407,7 @@ subroutine prep_glc_map_one_field_lnd2glc(egi, eli, fieldname, fractions_lx, map ! vertical gradient calculator. use vertical_gradient_calculator_continuous, only : vertical_gradient_calculator_continuous_type + use vertical_gradient_calculator_factory use glc_elevclass_mod, only : glc_get_num_elevation_classes, & glc_get_elevclass_bounds, glc_all_elevclass_strings use map_lnd2glc_mod, only : map_lnd2glc @@ -425,11 +426,10 @@ subroutine prep_glc_map_one_field_lnd2glc(egi, eli, fieldname, fractions_lx, map g2x_gx => component_get_c2x_cx(glc(egi)) - gradient_calculator = vertical_gradient_calculator_continuous_type( & + gradient_calculator = create_vertical_gradient_calculator_continuous( & attr_vect = l2gacc_lx(eli), & fieldname = fieldname, & toponame = 'Sl_topo', & - nelev = glc_get_num_elevation_classes(), & elevclass_names = glc_all_elevclass_strings(), & elevclass_bounds = glc_get_elevclass_bounds()) call map_lnd2glc(l2x_l = l2gacc_lx(eli), & @@ -439,6 +439,8 @@ subroutine prep_glc_map_one_field_lnd2glc(egi, eli, fieldname, fractions_lx, map gradient_calculator = gradient_calculator, & mapper = mapper, & l2x_g = l2x_gx(eli)) + ! FIXME(wjs, 2016-04-30) Remove this + call gradient_calculator%print_statistics() end subroutine prep_glc_map_one_field_lnd2glc From a0ab8f064d1f23c59b6171170fda2b358a0e80f1 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Sat, 30 Apr 2016 07:08:32 -0600 Subject: [PATCH 46/61] Make routine public Test suite: None Test baseline: N/A Test namelist changes: N/A Test status: N/A Fixes: None User interface changes?: No Code review: None --- driver_cpl/driver/vertical_gradient_calculator_factory.F90 | 1 + 1 file changed, 1 insertion(+) diff --git a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 index 7fcba603b6a..a31411598df 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 @@ -16,6 +16,7 @@ module vertical_gradient_calculator_factory private public :: create_vertical_gradient_calculator_2nd_order + public :: create_vertical_gradient_calculator_continuous ! The following routines are public just to support unit testing, and shouldn't be ! called from production code From 78ea018e36c6698b89160f726e4ff8e11e55f487 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Sat, 30 Apr 2016 11:28:38 -0600 Subject: [PATCH 47/61] Clean up plots Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- .../plot_gradient | 24 ++++++++++++++++--- ...vertical_gradient_calculator_continuous.pf | 16 ++++--------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient b/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient index b63c505a332..a6b2e337e09 100755 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient @@ -86,14 +86,32 @@ class GradientInfo: plt.plot(self.topo, self.field, 'ro') + # Limit upper bound of top elevation class + upper_bound = min(self.elevclass_bounds[self.nelev], + self.topo[self.nelev-1] + + (self.topo[self.nelev-1] - self.elevclass_bounds[self.nelev-1])) + for ec in range(self.nelev): + if (ec < self.nelev - 1): + my_upper_bound = self.elevclass_bounds[ec+1] + else: + my_upper_bound = upper_bound (xs, ys) = gradient_line(self.topo[ec], self.field[ec], self.gradient[ec], - self.elevclass_bounds[ec], self.elevclass_bounds[ec+1]) + self.elevclass_bounds[ec], my_upper_bound) plt.plot(xs, ys, 'b') - # plot elevation class bounds - vertical lines + # limit x axes + plt.xlim([self.elevclass_bounds[0], upper_bound]) + + # extend y axes a bit ylim = plt.axes().get_ylim() - for ec_bound in self.elevclass_bounds: + yrange = ylim[1] - ylim[0] + ylim = (ylim[0] - 0.02*yrange, ylim[1] + 0.02*yrange) + plt.ylim(ylim) + + # plot elevation class bounds - vertical lines + # (don't draw last one) + for ec_bound in self.elevclass_bounds[:len(self.elevclass_bounds)-1]: plt.plot([ec_bound, ec_bound], ylim, 'k') pylab.savefig(output_filename) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf index 0cd9e78155a..84bfaffdb7c 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -225,11 +225,9 @@ contains subroutine real_temp(this) class(TestVertGradCalcCont), intent(inout) :: this type(vertical_gradient_calculator_continuous_type) :: calculator - ! Using a max of 4000 in the top elevclass should give the same results as 10000, and - ! will make for a prettier figure real(r8), parameter :: elevclass_bounds(11) = & [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 1300._r8, & - 1600._r8, 2000._r8, 2500._r8, 3000._r8, 4000._r8] + 1600._r8, 2000._r8, 2500._r8, 3000._r8, 10000._r8] real(r8) :: topo(10) real(r8) :: data(10) real(r8) :: gradients(10) @@ -275,11 +273,9 @@ contains subroutine real_data1(this) class(TestVertGradCalcCont), intent(inout) :: this type(vertical_gradient_calculator_continuous_type) :: calculator - ! Using a max of 4000 in the top elevclass should give the same results as 10000, and - ! will make for a prettier figure real(r8), parameter :: elevclass_bounds(11) = & [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 1300._r8, & - 1600._r8, 2000._r8, 2500._r8, 3000._r8, 4000._r8] + 1600._r8, 2000._r8, 2500._r8, 3000._r8, 10000._r8] real(r8), parameter :: topo(10) = & [150.45797729492188_r8, 369.68896484375_r8, 618.4522705078125_r8, 776.9857177734375_r8, & 1205.492919921875_r8, 1372.2435302734375_r8, 1800.0_r8, 2250.0_r8, 2750.0_r8, 3500.0_r8] @@ -309,10 +305,8 @@ contains ! approximately equal SMB class(TestVertGradCalcCont), intent(inout) :: this type(vertical_gradient_calculator_continuous_type) :: calculator - ! Using a max of 4000 in the top elevclass should give the same results as 10000, and - ! will make for a prettier figure real(r8), parameter :: elevclass_bounds(7) = & - [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 3000._r8, 4000._r8] + [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 3000._r8, 10000._r8] real(r8), parameter :: topo(6) = & [150.45797729492188_r8, 369.68896484375_r8, 618.4522705078125_r8, 776.9857177734375_r8, & 2146.2894083658853_r8, & ! mean of elevation classes 5-9 @@ -341,11 +335,9 @@ contains subroutine real_data2(this) class(TestVertGradCalcCont), intent(inout) :: this type(vertical_gradient_calculator_continuous_type) :: calculator - ! Using a max of 4000 in the top elevclass should give the same results as 10000, and - ! will make for a prettier figure real(r8), parameter :: elevclass_bounds(11) = & [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 1300._r8, & - 1600._r8, 2000._r8, 2500._r8, 3000._r8, 4000._r8] + 1600._r8, 2000._r8, 2500._r8, 3000._r8, 10000._r8] real(r8), parameter :: topo(10) = & [100.0_r8, 300.0_r8, 553.73822021484375_r8, 843.978759765625_r8, 1152.2908935546875_r8, & 1450.9669189453125_r8, 1628.5628662109375_r8, 2250.0_r8, 2750.0_r8, 3500.0_r8] From cb4f44b9ac74abb9263f18666ce11e3d3b3c10dd Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Sat, 30 Apr 2016 12:44:15 -0600 Subject: [PATCH 48/61] Move tests that do output to their own file Only do these outputting tests if an environment variable is defined Also, change these tests to behave like the production code would - in particular, using the 2nd order vertical gradient calculator for initial guesses. Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...ertical_gradient_calculator_continuous.F90 | 2 + .../CMakeLists.txt | 15 ++ ...vertical_gradient_calculator_continuous.pf | 254 ++++++++++++++++++ ...vertical_gradient_calculator_continuous.pf | 188 ------------- 4 files changed, 271 insertions(+), 188 deletions(-) create mode 100644 driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 index 53304a09422..e4469e5b772 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 @@ -109,6 +109,8 @@ function constructor(field, topo, elevclass_bounds, calculator_initial_guess) & ! Initial guesses for gradients. We determine how good the solution is based on how ! well we match these initial guesses. We also fall back on these initial guesses if ! the gradient in a given elevation class is determined to be 'bad'. + ! + ! The calc_gradients method should not yet have been called on this object class(vertical_gradient_calculator_base_type), intent(in) :: calculator_initial_guess ! ! !LOCAL VARIABLES: diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt index ffe842381a1..0ce11af416b 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt @@ -4,6 +4,21 @@ set (pfunit_sources test_vertical_gradient_calculator_factory.pf ) +# Only include output_vertical_gradient_calculator_continuous.pf if the +# environment variable OUTPUT_VGCONT is set: The "tests" in here aren't true +# unit tests, but rather more like "functional unit tests" (printing output for +# various inputs, for later inspection). This environment variable mechanism is +# a kludge to prevent running these "tests" that do i/o most of the time, only +# including them when requested. +# +# However, this mechanism requires rerunning cmake if you set or unset this +# environment variable. +if (DEFINED ENV{OUTPUT_VGCONT}) + list(APPEND pfunit_sources + output_vertical_gradient_calculator_continuous.pf + ) +endif() + set (extra_sources vertical_gradient_calculator_continuousNoLimit.F90 ) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf new file mode 100644 index 00000000000..bca306fec34 --- /dev/null +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf @@ -0,0 +1,254 @@ +module output_vertical_gradient_calculator_continuous + + ! This module runs vertical_gradient_calculator_continuous in some integration-style + ! tests, printing out the results for each test case for later plotting. + ! + ! We don't actually do assertions on the results - they are just made available for + ! manual inspection later. However, we (ab)use pfunit to do this anyway. + + use pfunit_mod + use vertical_gradient_calculator_continuous + use vertical_gradient_calculator_2nd_order + use vertical_gradient_calculator_continuousNoLimit + use shr_kind_mod , only : r8 => shr_kind_r8 + + implicit none + + @TestCase + type, extends(TestCase) :: OutputVGCCont + contains + procedure :: setUp + procedure :: tearDown + procedure :: run_test + procedure :: write_output + end type OutputVGCCont + + real(r8), parameter :: tol = 1.e-13_r8 + + ! Standard elevation class bounds when running with 10 elevation classes + real(r8), parameter :: bounds_10ec(11) = & + [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 1300._r8, & + 1600._r8, 2000._r8, 2500._r8, 3000._r8, 10000._r8] + +contains + + ! ======================================================================== + ! Test helpers + ! ======================================================================== + + subroutine setUp(this) + class(OutputVGCCont), intent(inout) :: this + end subroutine setUp + + subroutine tearDown(this) + class(OutputVGCCont), intent(inout) :: this + end subroutine tearDown + + subroutine run_test(this, name, elevclass_bounds, topo, data, & + unlimited) + ! Set up and run a test with the given inputs, for a single point + ! + ! Also outputs results to a file + class(OutputVGCCont), intent(inout) :: this + character(len=*), intent(in) :: name + real(r8), intent(in) :: elevclass_bounds(:) + real(r8), intent(in) :: topo(:) + real(r8), intent(in) :: data(:) + + ! Whether to use the 'unlimited' variant of the calculator class; if not specified, + ! assumed false (i.e., assumed we are using the true version). + logical, intent(in), optional :: unlimited + + class(vertical_gradient_calculator_continuous_type), allocatable :: calculator + type(vertical_gradient_calculator_2nd_order_type) :: calculator_initial_guess + integer :: nelev + logical :: l_unlimited + real(r8), allocatable :: gradients(:) + ! ------------------------------------------------------------------------ + + if (present(unlimited)) then + l_unlimited = unlimited + else + l_unlimited = .false. + end if + + nelev = size(topo) + @assertEqual(nelev, size(data)) + @assertEqual(nelev+1, size(elevclass_bounds)) + + ! We could use the factory here (from the production code: + ! create_vertical_gradient_calculator_continuous), but (1) it would require packing + ! things into an attribute vector, and (2) it would be hard to put in the noLimit + ! version. + + calculator_initial_guess = vertical_gradient_calculator_2nd_order_type( & + field = reshape(data, [1, nelev]), & + topo = reshape(topo, [1, nelev]), & + elevclass_bounds = elevclass_bounds) + + if (l_unlimited) then + allocate(calculator, source = vgc_continuousNoLimit_type( & + field = reshape(data, [1, nelev]), & + topo = reshape(topo, [1, nelev]), & + elevclass_bounds = elevclass_bounds, & + calculator_initial_guess = calculator_initial_guess)) + else + allocate(calculator, source = vertical_gradient_calculator_continuous_type( & + field = reshape(data, [1, nelev]), & + topo = reshape(topo, [1, nelev]), & + elevclass_bounds = elevclass_bounds, & + calculator_initial_guess = calculator_initial_guess)) + end if + + call calculator%calc_gradients() + allocate(gradients(nelev)) + call calculator%get_gradients_one_point(point = 1, gradients = gradients) + + call this%write_output( & + name = name, & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + gradients = gradients) + + end subroutine run_test + + subroutine write_output(this, name, elevclass_bounds, topo, data, gradients) + class(OutputVGCCont), intent(inout) :: this + character(len=*), intent(in) :: name + real(r8), intent(in) :: elevclass_bounds(:) + real(r8), intent(in) :: topo(:) + real(r8), intent(in) :: data(:) + real(r8), intent(in) :: gradients(:) + + integer :: n_elev_classes + character(len=:), allocatable :: filename + character(len=32) :: bounds_format + character(len=32) :: data_format + + character(len=*), parameter :: filename_prefix = 'gradients_continuous_' + character(len=*), parameter :: filename_suffix = '.txt' + integer, parameter :: out_unit = 11 + + ! ------------------------------------------------------------------------ + + n_elev_classes = size(gradients) + @assertEqual(n_elev_classes + 1, size(elevclass_bounds)) + @assertEqual(n_elev_classes, size(topo)) + @assertEqual(n_elev_classes, size(data)) + + filename = filename_prefix // trim(name) // filename_suffix + open(out_unit, file=filename, action='write') + + write(bounds_format, '(a, i0, a, a)') '(', n_elev_classes + 1, 'f20.10', ')' + write(data_format, '(a, i0, a, a)') '(', n_elev_classes, 'f20.10', ')' + + write(out_unit, '(i0)') n_elev_classes + write(out_unit, bounds_format) elevclass_bounds + write(out_unit, data_format) topo + write(out_unit, data_format) data + write(out_unit, data_format) gradients + + close(out_unit) + end subroutine write_output + + ! ======================================================================== + ! Actual tests + ! ======================================================================== + + @Test + subroutine basic(this) + class(OutputVGCCont), intent(inout) :: this + + call this%run_test( & + name = 'basic', & + elevclass_bounds = [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8], & + topo = [10._r8, 30._r8, 50._r8, 70._r8, 90._r8], & + data = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8]) + end subroutine basic + + @Test + subroutine basic_noLimit(this) + class(OutputVGCCont), intent(inout) :: this + + call this%run_test( & + name = 'basic_noLimit', & + elevclass_bounds = [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8], & + topo = [10._r8, 30._r8, 50._r8, 70._r8, 90._r8], & + data = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8], & + unlimited = .true.) + end subroutine basic_noLimit + + ! FIXME(wjs, 2016-04-27) delete this + @Test + subroutine real_temp(this) + class(OutputVGCCont), intent(inout) :: this + real(r8) :: topo(10) + real(r8) :: data(10) + + topo(1) = 150.d0 + topo(2) = 370.d0 + topo(3) = 618.d0 + topo(4) = 777.d0 + topo(5) = 1205.d0 + topo(6) = 1372.d0 + topo(7) = 1800.d0 + topo(8) = 2250.d0 + topo(9) = 2750.d0 + topo(10)= 3500.d0 + + data(1) = -3.89d0 + data(2) = -2.42d0 + data(3) = -0.71d0 + data(4) = 0.00d0 + data(5) = 2.19d0 + data(6) = 2.19d0 + data(7) = 2.19d0 + data(8) = 2.19d0 + data(9) = 2.20d0 + data(10)= 2.74d0 + + call this%run_test( & + name = 'real_temp', & + elevclass_bounds = bounds_10ec, & + topo = topo, & + data = data) + end subroutine real_temp + + @Test + subroutine real_data1(this) + class(OutputVGCCont), intent(inout) :: this + + call this%run_test( & + name = 'real_data1', & + elevclass_bounds = bounds_10ec, & + + topo = [150.45797729492188_r8, 369.68896484375_r8, 618.4522705078125_r8, & + 776.9857177734375_r8, 1205.492919921875_r8, 1372.2435302734375_r8, & + 1800.0_r8, 2250.0_r8, 2750.0_r8, 3500.0_r8], & + + data = [-3.8940095691941679e-05_r8, -2.4159431632142514e-05_r8, -7.1326958277495578e-06_r8, & + 3.2833636254281373e-08_r8, 2.1934458345640451e-05_r8, 2.1910125724389218e-05_r8, & + 2.191431303799618e-05_r8, 2.1943444153293967e-05_r8, 2.1968355213175528e-05_r8, & + 2.7414380383561365e-05_r8]) + + end subroutine real_data1 + + @Test + subroutine real_data2(this) + class(OutputVGCCont), intent(inout) :: this + + call this%run_test( & + name = 'real_data2', & + elevclass_bounds = bounds_10ec, & + + topo = [100.0_r8, 300.0_r8, 553.73822021484375_r8, 843.978759765625_r8, 1152.2908935546875_r8, & + 1450.9669189453125_r8, 1628.5628662109375_r8, 2250.0_r8, 2750.0_r8, 3500.0_r8], & + + data = [-3.2589337934041396e-05_r8, -6.7787163970933761e-06_r8, 0.0_r8, & + 8.7906073531485163e-06_r8, 8.6524905782425776e-06_r8, 8.2202923294971697e-06_r8, & + 7.9119627116597258e-06_r8, 7.5692469181376509e-06_r8, 7.3112623795168474e-06_r8, & + 7.0084388426039368e-06_r8]) + end subroutine real_data2 + +end module output_vertical_gradient_calculator_continuous diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf index 84bfaffdb7c..05b2b7fc78c 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf @@ -20,26 +20,18 @@ module test_vertical_gradient_calculator_continuous procedure :: create_calculator_one_point procedure :: create_calculatorNoLimit procedure :: create_calculatorNoLimit_one_point - procedure :: write_output end type TestVertGradCalcCont real(r8), parameter :: tol = 1.e-13_r8 - integer, parameter :: out_unit = 11 - integer :: test_num = 0 contains subroutine setUp(this) class(TestVertGradCalcCont), intent(inout) :: this - character(len=32) :: filename - test_num = test_num + 1 - write(filename, '(a, i0, a)') 'gradients_continuous_', test_num, '.txt' - open(out_unit, file=filename, action='write') end subroutine setUp subroutine tearDown(this) class(TestVertGradCalcCont), intent(inout) :: this - close(out_unit) end subroutine tearDown function create_calculator(this, topo, data, elevclass_bounds) & @@ -136,32 +128,6 @@ contains elevclass_bounds = elevclass_bounds) end function create_calculatorNoLimit_one_point - subroutine write_output(this, elevclass_bounds, topo, data, gradients) - class(TestVertGradCalcCont), intent(inout) :: this - real(r8), intent(in) :: elevclass_bounds(:) - real(r8), intent(in) :: topo(:) - real(r8), intent(in) :: data(:) - real(r8), intent(in) :: gradients(:) - - integer :: n_elev_classes - character(len=32) :: bounds_format - character(len=32) :: data_format - - n_elev_classes = size(gradients) - @assertEqual(n_elev_classes + 1, size(elevclass_bounds)) - @assertEqual(n_elev_classes, size(topo)) - @assertEqual(n_elev_classes, size(data)) - - write(bounds_format, '(a, i0, a, a)') '(', n_elev_classes + 1, 'f20.10', ')' - write(data_format, '(a, i0, a, a)') '(', n_elev_classes, 'f20.10', ')' - - write(out_unit, '(i0)') n_elev_classes - write(out_unit, bounds_format) elevclass_bounds - write(out_unit, data_format) topo - write(out_unit, data_format) data - write(out_unit, data_format) gradients - end subroutine write_output - @Test subroutine basic(this) class(TestVertGradCalcCont), intent(inout) :: this @@ -182,13 +148,6 @@ contains expected_gradients(:) = [0._r8, 0.15_r8, 0.10_r8, 0.05_r8, 0.0_r8] @assertEqual(expected_gradients, gradients, tolerance=tol) - ! FIXME(wjs, 2016-04-27) Move this to somewhere else - call this%write_output( & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - gradients = gradients) - end subroutine basic @Test @@ -211,155 +170,8 @@ contains expected_gradients(:) = [0.2_r8, 0.15_r8, 0.10_r8, 0.05_r8, 0.0_r8] @assertEqual(expected_gradients, gradients, tolerance=tol) - ! FIXME(wjs, 2016-04-27) Move this to somewhere else - call this%write_output( & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - gradients = gradients) - end subroutine basic_noLimit - ! FIXME(wjs, 2016-04-27) delete this - @Test - subroutine real_temp(this) - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(11) = & - [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 1300._r8, & - 1600._r8, 2000._r8, 2500._r8, 3000._r8, 10000._r8] - real(r8) :: topo(10) - real(r8) :: data(10) - real(r8) :: gradients(10) - - topo(1) = 150.d0 - topo(2) = 370.d0 - topo(3) = 618.d0 - topo(4) = 777.d0 - topo(5) = 1205.d0 - topo(6) = 1372.d0 - topo(7) = 1800.d0 - topo(8) = 2250.d0 - topo(9) = 2750.d0 - topo(10)= 3500.d0 - - data(1) = -3.89d0 - data(2) = -2.42d0 - data(3) = -0.71d0 - data(4) = 0.00d0 - data(5) = 2.19d0 - data(6) = 2.19d0 - data(7) = 2.19d0 - data(8) = 2.19d0 - data(9) = 2.20d0 - data(10)= 2.74d0 - - - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%get_gradients_one_point(point = 1, gradients = gradients) - - call this%write_output( & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - gradients = gradients) - end subroutine real_temp - - ! FIXME(wjs, 2016-04-27) move this elsewhere - @Test - subroutine real_data1(this) - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(11) = & - [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 1300._r8, & - 1600._r8, 2000._r8, 2500._r8, 3000._r8, 10000._r8] - real(r8), parameter :: topo(10) = & - [150.45797729492188_r8, 369.68896484375_r8, 618.4522705078125_r8, 776.9857177734375_r8, & - 1205.492919921875_r8, 1372.2435302734375_r8, 1800.0_r8, 2250.0_r8, 2750.0_r8, 3500.0_r8] - real(r8), parameter :: data(10) = & - [-3.8940095691941679e-05_r8, -2.4159431632142514e-05_r8, -7.1326958277495578e-06_r8, & - 3.2833636254281373e-08_r8, 2.1934458345640451e-05_r8, 2.1910125724389218e-05_r8, & - 2.191431303799618e-05_r8, 2.1943444153293967e-05_r8, 2.1968355213175528e-05_r8, & - 2.7414380383561365e-05_r8] - real(r8) :: gradients(10) - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%get_gradients_one_point(point = 1, gradients = gradients) - - call this%write_output( & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - gradients = gradients) - end subroutine real_data1 - - ! FIXME(wjs, 2016-04-27) move this elsewhere - @Test - subroutine real_data1_combinedA(this) - ! See what happens when we combine the data from real_data1 that all have - ! approximately equal SMB - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(7) = & - [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 3000._r8, 10000._r8] - real(r8), parameter :: topo(6) = & - [150.45797729492188_r8, 369.68896484375_r8, 618.4522705078125_r8, 776.9857177734375_r8, & - 2146.2894083658853_r8, & ! mean of elevation classes 5-9 - 3500.0_r8] - real(r8), parameter :: data(6) = & - [-3.8940095691941679e-05_r8, -2.4159431632142514e-05_r8, -7.1326958277495578e-06_r8, & - 3.2833636254281373e-08_r8, & - 2.1934139294899068e-05_r8, & ! mean of elevation classes 5-9 - 2.7414380383561365e-05_r8] - real(r8) :: gradients(6) - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%get_gradients_one_point(point = 1, gradients = gradients) - - call this%write_output( & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - gradients = gradients) - end subroutine real_data1_combinedA - - ! FIXME(wjs, 2016-04-27) move this elsewhere - @Test - subroutine real_data2(this) - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(11) = & - [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 1300._r8, & - 1600._r8, 2000._r8, 2500._r8, 3000._r8, 10000._r8] - real(r8), parameter :: topo(10) = & - [100.0_r8, 300.0_r8, 553.73822021484375_r8, 843.978759765625_r8, 1152.2908935546875_r8, & - 1450.9669189453125_r8, 1628.5628662109375_r8, 2250.0_r8, 2750.0_r8, 3500.0_r8] - real(r8), parameter :: data(10) = & - [-3.2589337934041396e-05_r8, -6.7787163970933761e-06_r8, 0.0_r8, & - 8.7906073531485163e-06_r8, 8.6524905782425776e-06_r8, 8.2202923294971697e-06_r8, & - 7.9119627116597258e-06_r8, 7.5692469181376509e-06_r8, 7.3112623795168474e-06_r8, & - 7.0084388426039368e-06_r8] - real(r8) :: gradients(10) - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%get_gradients_one_point(point = 1, gradients = gradients) - - call this%write_output( & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - gradients = gradients) - end subroutine real_data2 - @Test subroutine topo_outOfBoundsHigh(this) class(TestVertGradCalcCont), intent(inout) :: this From c9281985f85bc1c05e7341db50ede5530dd1f778 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Sat, 30 Apr 2016 13:58:37 -0600 Subject: [PATCH 49/61] Output initial guess, unlimited and limited for each test Also remove old, temporary output tests Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...vertical_gradient_calculator_continuous.pf | 162 +++++------------- 1 file changed, 39 insertions(+), 123 deletions(-) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf index bca306fec34..e191b189c73 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf @@ -44,68 +44,68 @@ contains class(OutputVGCCont), intent(inout) :: this end subroutine tearDown - subroutine run_test(this, name, elevclass_bounds, topo, data, & - unlimited) + subroutine run_test(this, name, elevclass_bounds, topo, data) ! Set up and run a test with the given inputs, for a single point ! - ! Also outputs results to a file + ! Also outputs results to files + ! + ! Does separate outputs for: + ! - initialguess + ! - unlimited + ! - limited class(OutputVGCCont), intent(inout) :: this character(len=*), intent(in) :: name real(r8), intent(in) :: elevclass_bounds(:) real(r8), intent(in) :: topo(:) real(r8), intent(in) :: data(:) - ! Whether to use the 'unlimited' variant of the calculator class; if not specified, - ! assumed false (i.e., assumed we are using the true version). - logical, intent(in), optional :: unlimited - - class(vertical_gradient_calculator_continuous_type), allocatable :: calculator type(vertical_gradient_calculator_2nd_order_type) :: calculator_initial_guess + type(vgc_continuousNoLimit_type) :: calculator_unlimited + type(vertical_gradient_calculator_continuous_type) :: calculator_limited integer :: nelev - logical :: l_unlimited - real(r8), allocatable :: gradients(:) + real(r8) :: gradients(size(topo)) ! ------------------------------------------------------------------------ - if (present(unlimited)) then - l_unlimited = unlimited - else - l_unlimited = .false. - end if - nelev = size(topo) @assertEqual(nelev, size(data)) @assertEqual(nelev+1, size(elevclass_bounds)) - ! We could use the factory here (from the production code: - ! create_vertical_gradient_calculator_continuous), but (1) it would require packing - ! things into an attribute vector, and (2) it would be hard to put in the noLimit - ! version. - calculator_initial_guess = vertical_gradient_calculator_2nd_order_type( & field = reshape(data, [1, nelev]), & topo = reshape(topo, [1, nelev]), & elevclass_bounds = elevclass_bounds) + call calculator_initial_guess%calc_gradients() + call calculator_initial_guess%get_gradients_one_point(point = 1, gradients = gradients) + call this%write_output( & + name = trim(name) // '_initialguess', & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + gradients = gradients) - if (l_unlimited) then - allocate(calculator, source = vgc_continuousNoLimit_type( & - field = reshape(data, [1, nelev]), & - topo = reshape(topo, [1, nelev]), & - elevclass_bounds = elevclass_bounds, & - calculator_initial_guess = calculator_initial_guess)) - else - allocate(calculator, source = vertical_gradient_calculator_continuous_type( & - field = reshape(data, [1, nelev]), & - topo = reshape(topo, [1, nelev]), & - elevclass_bounds = elevclass_bounds, & - calculator_initial_guess = calculator_initial_guess)) - end if - - call calculator%calc_gradients() - allocate(gradients(nelev)) - call calculator%get_gradients_one_point(point = 1, gradients = gradients) + calculator_unlimited = vgc_continuousNoLimit_type( & + field = reshape(data, [1, nelev]), & + topo = reshape(topo, [1, nelev]), & + elevclass_bounds = elevclass_bounds, & + calculator_initial_guess = calculator_initial_guess) + call calculator_unlimited%calc_gradients() + call calculator_unlimited%get_gradients_one_point(point = 1, gradients = gradients) + call this%write_output( & + name = trim(name) // '_unlimited', & + elevclass_bounds = elevclass_bounds, & + topo = topo, & + data = data, & + gradients = gradients) + calculator_limited = vertical_gradient_calculator_continuous_type( & + field = reshape(data, [1, nelev]), & + topo = reshape(topo, [1, nelev]), & + elevclass_bounds = elevclass_bounds, & + calculator_initial_guess = calculator_initial_guess) + call calculator_limited%calc_gradients() + call calculator_limited%get_gradients_one_point(point = 1, gradients = gradients) call this%write_output( & - name = name, & + name = trim(name) // '_limited', & elevclass_bounds = elevclass_bounds, & topo = topo, & data = data, & @@ -167,88 +167,4 @@ contains data = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8]) end subroutine basic - @Test - subroutine basic_noLimit(this) - class(OutputVGCCont), intent(inout) :: this - - call this%run_test( & - name = 'basic_noLimit', & - elevclass_bounds = [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8], & - topo = [10._r8, 30._r8, 50._r8, 70._r8, 90._r8], & - data = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8], & - unlimited = .true.) - end subroutine basic_noLimit - - ! FIXME(wjs, 2016-04-27) delete this - @Test - subroutine real_temp(this) - class(OutputVGCCont), intent(inout) :: this - real(r8) :: topo(10) - real(r8) :: data(10) - - topo(1) = 150.d0 - topo(2) = 370.d0 - topo(3) = 618.d0 - topo(4) = 777.d0 - topo(5) = 1205.d0 - topo(6) = 1372.d0 - topo(7) = 1800.d0 - topo(8) = 2250.d0 - topo(9) = 2750.d0 - topo(10)= 3500.d0 - - data(1) = -3.89d0 - data(2) = -2.42d0 - data(3) = -0.71d0 - data(4) = 0.00d0 - data(5) = 2.19d0 - data(6) = 2.19d0 - data(7) = 2.19d0 - data(8) = 2.19d0 - data(9) = 2.20d0 - data(10)= 2.74d0 - - call this%run_test( & - name = 'real_temp', & - elevclass_bounds = bounds_10ec, & - topo = topo, & - data = data) - end subroutine real_temp - - @Test - subroutine real_data1(this) - class(OutputVGCCont), intent(inout) :: this - - call this%run_test( & - name = 'real_data1', & - elevclass_bounds = bounds_10ec, & - - topo = [150.45797729492188_r8, 369.68896484375_r8, 618.4522705078125_r8, & - 776.9857177734375_r8, 1205.492919921875_r8, 1372.2435302734375_r8, & - 1800.0_r8, 2250.0_r8, 2750.0_r8, 3500.0_r8], & - - data = [-3.8940095691941679e-05_r8, -2.4159431632142514e-05_r8, -7.1326958277495578e-06_r8, & - 3.2833636254281373e-08_r8, 2.1934458345640451e-05_r8, 2.1910125724389218e-05_r8, & - 2.191431303799618e-05_r8, 2.1943444153293967e-05_r8, 2.1968355213175528e-05_r8, & - 2.7414380383561365e-05_r8]) - - end subroutine real_data1 - - @Test - subroutine real_data2(this) - class(OutputVGCCont), intent(inout) :: this - - call this%run_test( & - name = 'real_data2', & - elevclass_bounds = bounds_10ec, & - - topo = [100.0_r8, 300.0_r8, 553.73822021484375_r8, 843.978759765625_r8, 1152.2908935546875_r8, & - 1450.9669189453125_r8, 1628.5628662109375_r8, 2250.0_r8, 2750.0_r8, 3500.0_r8], & - - data = [-3.2589337934041396e-05_r8, -6.7787163970933761e-06_r8, 0.0_r8, & - 8.7906073531485163e-06_r8, 8.6524905782425776e-06_r8, 8.2202923294971697e-06_r8, & - 7.9119627116597258e-06_r8, 7.5692469181376509e-06_r8, 7.3112623795168474e-06_r8, & - 7.0084388426039368e-06_r8]) - end subroutine real_data2 - end module output_vertical_gradient_calculator_continuous From 2087fb765bb766d4e21769684fcdc11df6b4a64a Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Sat, 30 Apr 2016 14:02:45 -0600 Subject: [PATCH 50/61] Go back to setting y range ourselves This makes it easier to compare plots Test suite: None Test baseline: N/A Test namelist changes: N/A Test status: N/A Fixes: None User interface changes?: No Code review: None --- .../plot_gradient | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient b/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient index a6b2e337e09..4720b2a764d 100755 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/plot_gradient @@ -103,16 +103,17 @@ class GradientInfo: # limit x axes plt.xlim([self.elevclass_bounds[0], upper_bound]) - # extend y axes a bit - ylim = plt.axes().get_ylim() - yrange = ylim[1] - ylim[0] - ylim = (ylim[0] - 0.02*yrange, ylim[1] + 0.02*yrange) - plt.ylim(ylim) + # set y axes ourselves, rather than letting them be dynamic, for easier + # comparison between figures + y_range = field_max - field_min + y_max = field_max + 0.2 * y_range + y_min = field_min - 0.2 * y_range + plt.ylim([y_min, y_max]) # plot elevation class bounds - vertical lines - # (don't draw last one) + # (don't draw upper bound of last EC) for ec_bound in self.elevclass_bounds[:len(self.elevclass_bounds)-1]: - plt.plot([ec_bound, ec_bound], ylim, 'k') + plt.plot([ec_bound, ec_bound], [y_min, y_max], 'k') pylab.savefig(output_filename) plt.close() From 7239f97274413a6bddf69d91716664a7430ce6b7 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Sat, 30 Apr 2016 14:50:23 -0600 Subject: [PATCH 51/61] Add a bunch of tests with real output Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- ...vertical_gradient_calculator_continuous.pf | 301 +++++++++++++++++- 1 file changed, 298 insertions(+), 3 deletions(-) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf index e191b189c73..3fc01001345 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf @@ -77,7 +77,7 @@ contains call calculator_initial_guess%calc_gradients() call calculator_initial_guess%get_gradients_one_point(point = 1, gradients = gradients) call this%write_output( & - name = trim(name) // '_initialguess', & + name = trim(name) // '_1initialguess', & elevclass_bounds = elevclass_bounds, & topo = topo, & data = data, & @@ -91,7 +91,7 @@ contains call calculator_unlimited%calc_gradients() call calculator_unlimited%get_gradients_one_point(point = 1, gradients = gradients) call this%write_output( & - name = trim(name) // '_unlimited', & + name = trim(name) // '_2unlimited', & elevclass_bounds = elevclass_bounds, & topo = topo, & data = data, & @@ -105,7 +105,7 @@ contains call calculator_limited%calc_gradients() call calculator_limited%get_gradients_one_point(point = 1, gradients = gradients) call this%write_output( & - name = trim(name) // '_limited', & + name = trim(name) // '_3limited', & elevclass_bounds = elevclass_bounds, & topo = topo, & data = data, & @@ -167,4 +167,299 @@ contains data = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8]) end subroutine basic + ! ------------------------------------------------------------------------ + ! The following are taken from a cpl hist file with date stamp 0002-01-01 from test + ! SMS_D_Ly1.f09_g16.TG.yellowstone_intel + ! + ! The name of the test gives the x_y coordinate + ! ------------------------------------------------------------------------ + + @Test + subroutine tg_year2_x237_y177(this) + class(OutputVGCCont), intent(inout) :: this + + call this%run_test( & + name = 'tg_year2_x237_y177', & + elevclass_bounds = bounds_10ec, & + topo = [ & + 100.00000000000000000_r8, & + 300.00000000000000000_r8, & + 525.87927246093750000_r8, & + 850.00000000000000000_r8, & + 1174.07202148437500000_r8, & + 1491.06066894531250000_r8, & + 1634.75134277343750000_r8, & + 2250.00000000000000000_r8, & + 2750.00000000000000000_r8, & + 3500.00000000000000000_r8], & + data = [ & + -0.00003017942981387_r8, & + 0.00000000000000000_r8, & + 0.00000517720582138_r8, & + 0.00001201005943585_r8, & + 0.00001159968905995_r8, & + 0.00001089518445951_r8, & + 0.00000945808187680_r8, & + 0.00000954108236328_r8, & + 0.00000874735724210_r8, & + 0.00000951678703132_r8] & + ) + end subroutine tg_year2_x237_y177 + + @Test + subroutine tg_year2_x254_y179(this) + class(OutputVGCCont), intent(inout) :: this + + call this%run_test( & + name = 'tg_year2_x254_y179', & + elevclass_bounds = bounds_10ec, & + topo = [ & + 100.00000000000000000_r8, & + 300.00000000000000000_r8, & + 550.00000000000000000_r8, & + 850.00000000000000000_r8, & + 1150.00000000000000000_r8, & + 1450.00000000000000000_r8, & + 1800.00000000000000000_r8, & + 2432.00463867187500000_r8, & + 2512.73315429687500000_r8, & + 3500.00000000000000000_r8], & + data = [ & + -0.00004915712270304_r8, & + -0.00003682274473249_r8, & + -0.00002054757533188_r8, & + 0.00000890800856723_r8, & + 0.00000912497216632_r8, & + 0.00000876096328284_r8, & + 0.00000813312544778_r8, & + 0.00000723849916540_r8, & + 0.00000716611339158_r8, & + 0.00000634025218460_r8] & + ) + end subroutine tg_year2_x254_y179 + + @Test + subroutine tg_year2_x269_y179(this) + class(OutputVGCCont), intent(inout) :: this + + call this%run_test( & + name = 'tg_year2_x269_y179', & + elevclass_bounds = bounds_10ec, & + topo = [ & + 100.00000000000000000_r8, & + 300.00000000000000000_r8, & + 673.48120117187500000_r8, & + 891.23760986328125000_r8, & + 1033.81237792968750000_r8, & + 1450.00000000000000000_r8, & + 1800.00000000000000000_r8, & + 2250.00000000000000000_r8, & + 2750.00000000000000000_r8, & + 3500.00000000000000000_r8], & + data = [ & + -0.00004158946831012_r8, & + -0.00002728389881668_r8, & + -0.00000371834676116_r8, & + -0.00000029612721164_r8, & + -0.00000001078467626_r8, & + -0.00000063053033728_r8, & + -0.00000042971075231_r8, & + -0.00000006648337347_r8, & + 0.00000000000000000_r8, & + 0.00001133324531111_r8] & + ) + end subroutine tg_year2_x269_y179 + + @Test + subroutine tg_year2_x245_y171(this) + class(OutputVGCCont), intent(inout) :: this + + call this%run_test( & + name = 'tg_year2_x245_y171', & + elevclass_bounds = bounds_10ec, & + topo = [ & + 100.00000000000000000_r8, & + 300.00000000000000000_r8, & + 550.00000000000000000_r8, & + 850.00000000000000000_r8, & + 1150.00000000000000000_r8, & + 1450.00000000000000000_r8, & + 1800.00000000000000000_r8, & + 2250.00000000000000000_r8, & + 2750.00000000000000000_r8, & + 3500.00000000000000000_r8], & + data = [ & + -0.00006216453766683_r8, & + -0.00005053480708739_r8, & + -0.00003879935684381_r8, & + -0.00002813775790855_r8, & + -0.00000682415202391_r8, & + 0.00000000000000000_r8, & + 0.00000000000000000_r8, & + 0.00000581615859119_r8, & + 0.00000578505614612_r8, & + 0.00000704487820258_r8] & + ) + end subroutine tg_year2_x245_y171 + + @Test + subroutine tg_year2_x253_y171(this) + class(OutputVGCCont), intent(inout) :: this + + call this%run_test( & + name = 'tg_year2_x253_y171', & + elevclass_bounds = bounds_10ec, & + topo = [ & + 100.00000000000000000_r8, & + 300.00000000000000000_r8, & + 550.00000000000000000_r8, & + 850.00000000000000000_r8, & + 1150.00000000000000000_r8, & + 1450.00000000000000000_r8, & + 1800.00000000000000000_r8, & + 2250.00000000000000000_r8, & + 2741.18652343750000000_r8, & + 3500.00000000000000000_r8], & + data = [ & + -0.00005745654925704_r8, & + -0.00004694490053225_r8, & + -0.00003445266702329_r8, & + 0.00000570591555515_r8, & + 0.00001267663446924_r8, & + 0.00001288491057494_r8, & + 0.00001228994733538_r8, & + 0.00001145447004092_r8, & + 0.00001063432864612_r8, & + 0.00000983382778941_r8] & + ) + end subroutine tg_year2_x253_y171 + + @Test + subroutine tg_year2_x266_y173(this) + class(OutputVGCCont), intent(inout) :: this + + call this%run_test( & + name = 'tg_year2_x266_y173', & + elevclass_bounds = bounds_10ec, & + topo = [ & + 100.00000000000000000_r8, & + 300.00000000000000000_r8, & + 550.00000000000000000_r8, & + 871.65301513671875000_r8, & + 1211.49768066406250000_r8, & + 1432.28137207031250000_r8, & + 1665.71423339843750000_r8, & + 2250.00000000000000000_r8, & + 2750.00000000000000000_r8, & + 3500.00000000000000000_r8], & + data = [ & + -0.00006089130329201_r8, & + -0.00005163572859601_r8, & + -0.00004160191747360_r8, & + -0.00002927129935415_r8, & + -0.00001998909647227_r8, & + -0.00001512261678727_r8, & + -0.00001263937429030_r8, & + -0.00000406959406973_r8, & + 0.00000000000000000_r8, & + 0.00001004394562187_r8] & + ) + end subroutine tg_year2_x266_y173 + + @Test + subroutine tg_year2_x247_y164(this) + class(OutputVGCCont), intent(inout) :: this + + call this%run_test( & + name = 'tg_year2_x247_y164', & + elevclass_bounds = bounds_10ec, & + topo = [ & + 100.00000000000000000_r8, & + 300.00000000000000000_r8, & + 550.00000000000000000_r8, & + 850.00000000000000000_r8, & + 1150.00000000000000000_r8, & + 1450.00000000000000000_r8, & + 1800.00000000000000000_r8, & + 2250.00000000000000000_r8, & + 2750.00000000000000000_r8, & + 3500.00000000000000000_r8], & + data = [ & + -0.00008932914352044_r8, & + -0.00007648371683899_r8, & + -0.00006422778824344_r8, & + -0.00004912317308481_r8, & + -0.00003472463868093_r8, & + -0.00000160152967510_r8, & + 0.00000921983701119_r8, & + 0.00000909015761863_r8, & + 0.00001246667034138_r8, & + 0.00001318262184213_r8] & + ) + end subroutine tg_year2_x247_y164 + + @Test + subroutine tg_year2_x251_y164(this) + class(OutputVGCCont), intent(inout) :: this + + call this%run_test( & + name = 'tg_year2_x251_y164', & + elevclass_bounds = bounds_10ec, & + topo = [ & + 100.00000000000000000_r8, & + 300.00000000000000000_r8, & + 550.00000000000000000_r8, & + 850.00000000000000000_r8, & + 1150.00000000000000000_r8, & + 1450.00000000000000000_r8, & + 1800.00000000000000000_r8, & + 2441.64257812500000000_r8, & + 2564.37524414062500000_r8, & + 3500.00000000000000000_r8], & + data = [ & + -0.00010203790589003_r8, & + -0.00008802362572169_r8, & + -0.00006545173528139_r8, & + -0.00003821765130851_r8, & + -0.00001615641485841_r8, & + 0.00000000000000000_r8, & + 0.00001520295427326_r8, & + 0.00001540742596262_r8, & + 0.00001525775314803_r8, & + 0.00001418547435605_r8] & + ) + end subroutine tg_year2_x251_y164 + + @Test + subroutine tg_year2_x255_y163(this) + class(OutputVGCCont), intent(inout) :: this + + call this%run_test( & + name = 'tg_year2_x255_y163', & + elevclass_bounds = bounds_10ec, & + topo = [ & + 100.00000000000000000_r8, & + 277.44519042968750000_r8, & + 561.45306396484375000_r8, & + 830.48663330078125000_r8, & + 1167.21862792968750000_r8, & + 1373.33569335937500000_r8, & + 1800.00000000000000000_r8, & + 2250.00000000000000000_r8, & + 2750.00000000000000000_r8, & + 3500.00000000000000000_r8], & + data = [ & + -0.00002278820284118_r8, & + -0.00000824440030556_r8, & + 0.00000927457949729_r8, & + 0.00002404907900200_r8, & + 0.00003126181036350_r8, & + 0.00003124762952211_r8, & + 0.00003117840242339_r8, & + 0.00003535178620950_r8, & + 0.00003866513725370_r8, & + 0.00004323109533289_r8] & + ) + end subroutine tg_year2_x255_y163 + end module output_vertical_gradient_calculator_continuous From 3cb7c2a1068a138557c411c928f5832ab93832d7 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 4 May 2016 09:13:38 -0600 Subject: [PATCH 52/61] re add #118 correction to shr_orb_cosz --- share/csm_share/shr/shr_orb_mod.F90 | 165 ++++++++++++++-------------- 1 file changed, 82 insertions(+), 83 deletions(-) diff --git a/share/csm_share/shr/shr_orb_mod.F90 b/share/csm_share/shr/shr_orb_mod.F90 index a33400bca92..a7258efde22 100644 --- a/share/csm_share/shr/shr_orb_mod.F90 +++ b/share/csm_share/shr/shr_orb_mod.F90 @@ -21,7 +21,7 @@ MODULE shr_orb_mod public :: shr_orb_decl public :: shr_orb_print - real (SHR_KIND_R8),public,parameter :: SHR_ORB_UNDEF_REAL = 1.e36_SHR_KIND_R8 ! undefined real + real (SHR_KIND_R8),public,parameter :: SHR_ORB_UNDEF_REAL = 1.e36_SHR_KIND_R8 ! undefined real integer(SHR_KIND_IN),public,parameter :: SHR_ORB_UNDEF_INT = 2000000000 ! undefined int !---------------------------------------------------------------------------- @@ -61,7 +61,7 @@ real(SHR_KIND_R8) pure FUNCTION shr_orb_cosz(jday,lat,lon,declin,dt_avg) real (SHR_KIND_R8),intent(in) :: lat ! Centered latitude (radians) real (SHR_KIND_R8),intent(in) :: lon ! Centered longitude (radians) real (SHR_KIND_R8),intent(in) :: declin ! Solar declination (radians) - real (SHR_KIND_R8),intent(in), optional :: dt_avg ! if present and set non-zero, then use in the + real (SHR_KIND_R8),intent(in), optional :: dt_avg ! if present and set non-zero, then use in the ! average cosz calculation logical :: use_dt_avg @@ -78,9 +78,8 @@ real(SHR_KIND_R8) pure FUNCTION shr_orb_cosz(jday,lat,lon,declin,dt_avg) shr_orb_cosz = shr_orb_avg_cosz(jday, lat, lon, declin, dt_avg) else shr_orb_cosz = sin(lat)*sin(declin) - & - & cos(lat)*cos(declin)*cos(jday*2.0_SHR_KIND_R8*pi + lon) -! & cos(lat)*cos(declin) * & -! & cos((jday-floor(jday))*2.0_SHR_KIND_R8*pi + lon) + cos(lat)*cos(declin) * & + cos((jday-floor(jday))*2.0_SHR_KIND_R8*pi + lon) end if END FUNCTION shr_orb_cosz @@ -224,9 +223,9 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & !------------------------------------------------------------------------------- ! -! Calculate earths orbital parameters using Dave Threshers formula which -! came from Berger, Andre. 1978 "A Simple Algorithm to Compute Long-Term -! Variations of Daily Insolation". Contribution 18, Institute of Astronomy +! Calculate earths orbital parameters using Dave Threshers formula which +! came from Berger, Andre. 1978 "A Simple Algorithm to Compute Long-Term +! Variations of Daily Insolation". Contribution 18, Institute of Astronomy ! and Geophysics, Universite Catholique de Louvain, Louvain-la-Neuve, Belgium ! !------------------------------Code history------------------------------------- @@ -258,10 +257,10 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & real (SHR_KIND_R8) :: yb4_1950AD ! number of years before 1950 AD character(len=*),parameter :: subname = '(shr_orb_params)' - + ! Cosine series data for computation of obliquity: amplitude (arc seconds), ! rate (arc seconds/year), phase (degrees). - + real (SHR_KIND_R8), parameter :: obamp(poblen) = & ! amplitudes for obliquity cos series & (/ -2462.2214466_SHR_KIND_R8, -857.3232075_SHR_KIND_R8, -629.3231835_SHR_KIND_R8, & & -414.2804924_SHR_KIND_R8, -311.7632587_SHR_KIND_R8, 308.9408604_SHR_KIND_R8, & @@ -279,7 +278,7 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & & -1.5428851_SHR_KIND_R8, 1.4738838_SHR_KIND_R8, -1.4593669_SHR_KIND_R8, & & 1.4192259_SHR_KIND_R8, -1.1818980_SHR_KIND_R8, 1.1756474_SHR_KIND_R8, & & -1.1316126_SHR_KIND_R8, 1.0896928_SHR_KIND_R8/) - + real (SHR_KIND_R8), parameter :: obrate(poblen) = & ! rates for obliquity cosine series & (/ 31.609974_SHR_KIND_R8, 32.620504_SHR_KIND_R8, 24.172203_SHR_KIND_R8, & & 31.983787_SHR_KIND_R8, 44.828336_SHR_KIND_R8, 30.973257_SHR_KIND_R8, & @@ -297,7 +296,7 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & & 48.344406_SHR_KIND_R8, 55.145460_SHR_KIND_R8, 69.000539_SHR_KIND_R8, & & 11.071350_SHR_KIND_R8, 74.291298_SHR_KIND_R8, 11.047742_SHR_KIND_R8, & & 0.636717_SHR_KIND_R8, 12.844549_SHR_KIND_R8/) - + real (SHR_KIND_R8), parameter :: obphas(poblen) = & ! phases for obliquity cosine series & (/ 251.9025_SHR_KIND_R8, 280.8325_SHR_KIND_R8, 128.3057_SHR_KIND_R8, & & 292.7252_SHR_KIND_R8, 15.3747_SHR_KIND_R8, 263.7951_SHR_KIND_R8, & @@ -315,11 +314,11 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & & 256.6114_SHR_KIND_R8, 32.1008_SHR_KIND_R8, 143.6804_SHR_KIND_R8, & & 16.8784_SHR_KIND_R8, 160.6835_SHR_KIND_R8, 27.5932_SHR_KIND_R8, & & 348.1074_SHR_KIND_R8, 82.6496_SHR_KIND_R8/) - - ! Cosine/sine series data for computation of eccentricity and fixed vernal - ! equinox longitude of perihelion (fvelp): amplitude, + + ! Cosine/sine series data for computation of eccentricity and fixed vernal + ! equinox longitude of perihelion (fvelp): amplitude, ! rate (arc seconds/year), phase (degrees). - + real (SHR_KIND_R8), parameter :: ecamp (pecclen) = & ! ampl for eccen/fvelp cos/sin series & (/ 0.01860798_SHR_KIND_R8, 0.01627522_SHR_KIND_R8, -0.01300660_SHR_KIND_R8, & & 0.00988829_SHR_KIND_R8, -0.00336700_SHR_KIND_R8, 0.00333077_SHR_KIND_R8, & @@ -328,7 +327,7 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & & 0.00037800_SHR_KIND_R8, -0.00033700_SHR_KIND_R8, 0.00027600_SHR_KIND_R8, & & 0.00018200_SHR_KIND_R8, -0.00017400_SHR_KIND_R8, -0.00012400_SHR_KIND_R8, & & 0.00001250_SHR_KIND_R8/) - + real (SHR_KIND_R8), parameter :: ecrate(pecclen) = & ! rates for eccen/fvelp cos/sin series & (/ 4.2072050_SHR_KIND_R8, 7.3460910_SHR_KIND_R8, 17.8572630_SHR_KIND_R8, & & 17.2205460_SHR_KIND_R8, 16.8467330_SHR_KIND_R8, 5.1990790_SHR_KIND_R8, & @@ -337,7 +336,7 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & & 18.4939800_SHR_KIND_R8, 6.1909530_SHR_KIND_R8, 18.8677930_SHR_KIND_R8, & & 17.4255670_SHR_KIND_R8, 6.1860010_SHR_KIND_R8, 18.4174410_SHR_KIND_R8, & & 0.6678630_SHR_KIND_R8/) - + real (SHR_KIND_R8), parameter :: ecphas(pecclen) = & ! phases for eccen/fvelp cos/sin series & (/ 28.620089_SHR_KIND_R8, 193.788772_SHR_KIND_R8, 308.307024_SHR_KIND_R8, & & 320.199637_SHR_KIND_R8, 279.376984_SHR_KIND_R8, 87.195000_SHR_KIND_R8, & @@ -346,11 +345,11 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & & 296.414411_SHR_KIND_R8, 145.769910_SHR_KIND_R8, 337.237063_SHR_KIND_R8, & & 152.092288_SHR_KIND_R8, 126.839891_SHR_KIND_R8, 210.667199_SHR_KIND_R8, & & 72.108838_SHR_KIND_R8/) - - ! Sine series data for computation of moving vernal equinox longitude of - ! perihelion: amplitude (arc seconds), rate (arc sec/year), phase (degrees). - - real (SHR_KIND_R8), parameter :: mvamp (pmvelen) = & ! amplitudes for mvelp sine series + + ! Sine series data for computation of moving vernal equinox longitude of + ! perihelion: amplitude (arc seconds), rate (arc sec/year), phase (degrees). + + real (SHR_KIND_R8), parameter :: mvamp (pmvelen) = & ! amplitudes for mvelp sine series & (/ 7391.0225890_SHR_KIND_R8, 2555.1526947_SHR_KIND_R8, 2022.7629188_SHR_KIND_R8, & & -1973.6517951_SHR_KIND_R8, 1240.2321818_SHR_KIND_R8, 953.8679112_SHR_KIND_R8, & & -931.7537108_SHR_KIND_R8, 872.3795383_SHR_KIND_R8, 606.3544732_SHR_KIND_R8, & @@ -377,8 +376,8 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & & 11.6018181_SHR_KIND_R8, -11.2617293_SHR_KIND_R8, -10.4664199_SHR_KIND_R8, & & 10.4333970_SHR_KIND_R8, -10.2377466_SHR_KIND_R8, 10.1934446_SHR_KIND_R8, & & -10.1280191_SHR_KIND_R8, 10.0289441_SHR_KIND_R8, -10.0034259_SHR_KIND_R8/) - - real (SHR_KIND_R8), parameter :: mvrate(pmvelen) = & ! rates for mvelp sine series + + real (SHR_KIND_R8), parameter :: mvrate(pmvelen) = & ! rates for mvelp sine series & (/ 31.609974_SHR_KIND_R8, 32.620504_SHR_KIND_R8, 24.172203_SHR_KIND_R8, & & 0.636717_SHR_KIND_R8, 31.983787_SHR_KIND_R8, 3.138886_SHR_KIND_R8, & & 30.973257_SHR_KIND_R8, 44.828336_SHR_KIND_R8, 0.991874_SHR_KIND_R8, & @@ -433,7 +432,7 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & & 213.5577_SHR_KIND_R8, 154.1631_SHR_KIND_R8, 232.7153_SHR_KIND_R8, & & 138.3034_SHR_KIND_R8, 204.6609_SHR_KIND_R8, 106.5938_SHR_KIND_R8, & & 250.4676_SHR_KIND_R8, 332.3345_SHR_KIND_R8, 27.3039_SHR_KIND_R8/) - + !---------------------------Local variables---------------------------------- integer(SHR_KIND_IN) :: i ! Index for series summations real (SHR_KIND_R8) :: obsum ! Obliquity series summation @@ -454,17 +453,17 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & !---------------------------------------------------------------------------- ! radinp and algorithms below will need a degree to radian conversion factor - + if ( log_print .and. s_loglev > 0 ) then write(s_logunit,F00) 'Calculate characteristics of the orbit:' end if - + ! Check for flag to use input orbit parameters - + IF ( iyear_AD == SHR_ORB_UNDEF_INT ) THEN ! Check input obliq, eccen, and mvelp to ensure reasonable - + if( obliq == SHR_ORB_UNDEF_REAL )then write(s_logunit,F00) trim(subname)//' Have to specify orbital parameters:' write(s_logunit,F00) 'Either set: iyear_AD, OR [obliq, eccen, and mvelp]:' @@ -494,7 +493,7 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & eccen3 = eccen2*eccen ELSE ! Otherwise calculate based on years before present - + if ( log_print .and. s_loglev > 0) then write(s_logunit,F01) 'Calculate orbit for year: ' , iyear_AD end if @@ -506,7 +505,7 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & write(s_logunit,F01) 'Year to simulate was : ',iyear_AD call shr_sys_abort(subname//' ERROR: unreasonable year') end if - + ! The following calculates the earths obliquity, orbital eccentricity ! (and various powers of it) and vernal equinox mean longitude of ! perihelion for years in the past (future = negative of years past), @@ -528,10 +527,10 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & ! 5-10 million year solution. ! ! Years to time of interest must be negative of years before present - ! (1950) in formulas that follow. - + ! (1950) in formulas that follow. + years = - yb4_1950AD - + ! In the summations below, cosine or sine arguments, which end up in ! degrees, must be converted to radians via multiplication by degrad. ! @@ -540,38 +539,38 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & ! degrees via multiplication by psecdeg (arc seconds to degrees conversion ! factor). For obliq, first term is Berger 1978 epsilon star; second ! term is series summation in degrees. - + obsum = 0.0_SHR_KIND_R8 do i = 1, poblen obsum = obsum + obamp(i)*psecdeg*cos((obrate(i)*psecdeg*years + & & obphas(i))*degrad) end do obliq = 23.320556_SHR_KIND_R8 + obsum - - ! Summation of cosine and sine series for computation of eccentricity - ! (eccen; e in Berger 1978) and fixed vernal equinox longitude of - ! perihelion (fvelp; pi in Berger 1978), which is used for computation - ! of moving vernal equinox longitude of perihelion. Convert the rates, + + ! Summation of cosine and sine series for computation of eccentricity + ! (eccen; e in Berger 1978) and fixed vernal equinox longitude of + ! perihelion (fvelp; pi in Berger 1978), which is used for computation + ! of moving vernal equinox longitude of perihelion. Convert the rates, ! which are in arc seconds, into degrees via multiplication by psecdeg. - + cossum = 0.0_SHR_KIND_R8 do i = 1, pecclen cossum = cossum+ecamp(i)*cos((ecrate(i)*psecdeg*years+ecphas(i))*degrad) end do - + sinsum = 0.0_SHR_KIND_R8 do i = 1, pecclen sinsum = sinsum+ecamp(i)*sin((ecrate(i)*psecdeg*years+ecphas(i))*degrad) end do - + ! Use summations to calculate eccentricity - + eccen2 = cossum*cossum + sinsum*sinsum eccen = sqrt(eccen2) eccen3 = eccen2*eccen - + ! A series of cases for fvelp, which is in radians. - + if (abs(cossum) .le. 1.0E-8_SHR_KIND_R8) then if (sinsum .eq. 0.0_SHR_KIND_R8) then fvelp = 0.0_SHR_KIND_R8 @@ -589,25 +588,25 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & fvelp = atan(sinsum/cossum) endif endif - + ! Summation of sin series for computation of moving vernal equinox long ! of perihelion (mvelp; omega bar in Berger 1978) in degrees. For mvelp, - ! first term is fvelp in degrees; second term is Berger 1978 psi bar - ! times years and in degrees; third term is Berger 1978 zeta; fourth + ! first term is fvelp in degrees; second term is Berger 1978 psi bar + ! times years and in degrees; third term is Berger 1978 zeta; fourth ! term is series summation in degrees. Convert the amplitudes and rates, - ! which are in arc seconds, into degrees via multiplication by psecdeg. + ! which are in arc seconds, into degrees via multiplication by psecdeg. ! Series summation plus second and third terms constitute Berger 1978 ! psi, which is the general precession. - + mvsum = 0.0_SHR_KIND_R8 do i = 1, pmvelen mvsum = mvsum + mvamp(i)*psecdeg*sin((mvrate(i)*psecdeg*years + & & mvphas(i))*degrad) end do mvelp = fvelp/degrad + 50.439273_SHR_KIND_R8*psecdeg*years + 3.392506_SHR_KIND_R8 + mvsum - + ! Cases to make sure mvelp is between 0 and 360. - + do while (mvelp .lt. 0.0_SHR_KIND_R8) mvelp = mvelp + 360.0_SHR_KIND_R8 end do @@ -616,11 +615,11 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & end do END IF ! end of test on whether to calculate or use input orbital params - + ! Orbit needs the obliquity in radians - + obliqr = obliq*degrad - + ! 180 degrees must be added to mvelp since observations are made from the ! earth and the sun is considered (wrongly for the algorithm) to go around ! the earth. For a more graphic explanation see Appendix B in: @@ -630,22 +629,22 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & ! ! Additionally, orbit will need this value in radians. So mvelp becomes ! mvelpp (mvelp plus pi) - + mvelpp = (mvelp + 180._SHR_KIND_R8)*degrad - + ! Set up an argument used several times in lambm0 calculation ahead. - + beta = sqrt(1._SHR_KIND_R8 - eccen2) - + ! The mean longitude at the vernal equinox (lambda m nought in Berger - ! 1978; in radians) is calculated from the following formula given in + ! 1978; in radians) is calculated from the following formula given in ! Berger 1978. At the vernal equinox the true longitude (lambda in Berger ! 1978) is 0. lambm0 = 2._SHR_KIND_R8*((.5_SHR_KIND_R8*eccen + .125_SHR_KIND_R8*eccen3)*(1._SHR_KIND_R8 + beta)*sin(mvelpp) & & - .250_SHR_KIND_R8*eccen2*(.5_SHR_KIND_R8 + beta)*sin(2._SHR_KIND_R8*mvelpp) & & + .125_SHR_KIND_R8*eccen3*(1._SHR_KIND_R8/3._SHR_KIND_R8 + beta)*sin(3._SHR_KIND_R8*mvelpp)) - + if ( log_print ) then write(s_logunit,F03) '------ Computed Orbital Parameters ------' write(s_logunit,F03) 'Eccentricity = ',eccen @@ -656,7 +655,7 @@ SUBROUTINE shr_orb_params( iyear_AD , eccen , obliq , mvelp , & write(s_logunit,F03) 'Long at v.e.(rad) = ',lambm0 write(s_logunit,F03) '-----------------------------------------' end if - + END SUBROUTINE shr_orb_params !=============================================================================== @@ -679,24 +678,24 @@ SUBROUTINE shr_orb_decl(calday ,eccen ,mvelpp ,lambm0 ,obliqr ,delta ,eccf) real (SHR_KIND_R8),intent(in) :: calday ! Calendar day, including fraction real (SHR_KIND_R8),intent(in) :: eccen ! Eccentricity real (SHR_KIND_R8),intent(in) :: obliqr ! Earths obliquity in radians - real (SHR_KIND_R8),intent(in) :: lambm0 ! Mean long of perihelion at the + real (SHR_KIND_R8),intent(in) :: lambm0 ! Mean long of perihelion at the ! vernal equinox (radians) real (SHR_KIND_R8),intent(in) :: mvelpp ! moving vernal equinox longitude ! of perihelion plus pi (radians) real (SHR_KIND_R8),intent(out) :: delta ! Solar declination angle in rad real (SHR_KIND_R8),intent(out) :: eccf ! Earth-sun distance factor (ie. (1/r)**2) - + !---------------------------Local variables----------------------------- real (SHR_KIND_R8),parameter :: dayspy = 365.0_SHR_KIND_R8 ! days per year real (SHR_KIND_R8),parameter :: ve = 80.5_SHR_KIND_R8 ! Calday of vernal equinox ! assumes Jan 1 = calday 1 - + real (SHR_KIND_R8) :: lambm ! Lambda m, mean long of perihelion (rad) real (SHR_KIND_R8) :: lmm ! Intermediate argument involving lambm real (SHR_KIND_R8) :: lamb ! Lambda, the earths long of perihelion real (SHR_KIND_R8) :: invrho ! Inverse normalized sun/earth distance real (SHR_KIND_R8) :: sinl ! Sine of lmm - + ! Compute eccentricity factor and solar declination using ! day value where a round day (such as 213.0) refers to 0z at ! Greenwich longitude. @@ -705,7 +704,7 @@ SUBROUTINE shr_orb_decl(calday ,eccen ,mvelpp ,lambm0 ,obliqr ,delta ,eccf) ! Insolation and Quaternary Climatic Changes. J. of the Atmo. Sci. ! 35:2362-2367. ! - ! To get the earths true longitude (position in orbit; lambda in Berger + ! To get the earths true longitude (position in orbit; lambda in Berger ! 1978) which is necessary to find the eccentricity factor and declination, ! must first calculate the mean longitude (lambda m in Berger 1978) at ! the present day. This is done by adding to lambm0 (the mean longitude @@ -713,32 +712,32 @@ SUBROUTINE shr_orb_decl(calday ,eccen ,mvelpp ,lambm0 ,obliqr ,delta ,eccf) ! an increment (delta lambda m in Berger 1978) that is the number of ! days past or before (a negative increment) the vernal equinox divided by ! the days in a model year times the 2*pi radians in a complete orbit. - + lambm = lambm0 + (calday - ve)*2._SHR_KIND_R8*pi/dayspy lmm = lambm - mvelpp - + ! The earths true longitude, in radians, is then found from ! the formula in Berger 1978: - + sinl = sin(lmm) lamb = lambm + eccen*(2._SHR_KIND_R8*sinl + eccen*(1.25_SHR_KIND_R8*sin(2._SHR_KIND_R8*lmm) & & + eccen*((13.0_SHR_KIND_R8/12.0_SHR_KIND_R8)*sin(3._SHR_KIND_R8*lmm) - 0.25_SHR_KIND_R8*sinl))) - + ! Using the obliquity, eccentricity, moving vernal equinox longitude of ! perihelion (plus), and earths true longitude, the declination (delta) ! and the normalized earth/sun distance (rho in Berger 1978; actually inverse - ! rho will be used), and thus the eccentricity factor (eccf), can be + ! rho will be used), and thus the eccentricity factor (eccf), can be ! calculated from formulas given in Berger 1978. - + invrho = (1._SHR_KIND_R8 + eccen*cos(lamb - mvelpp)) / (1._SHR_KIND_R8 - eccen*eccen) - + ! Set solar declination and eccentricity factor - + delta = asin(sin(obliqr)*sin(lamb)) eccf = invrho*invrho - + return - + END SUBROUTINE shr_orb_decl !=============================================================================== @@ -758,9 +757,9 @@ SUBROUTINE shr_orb_print( iyear_AD, eccen, obliq, mvelp ) !---------------------------Arguments---------------------------------------- integer(SHR_KIND_IN),intent(in) :: iyear_AD ! requested Year (AD) - real (SHR_KIND_R8),intent(in) :: eccen ! eccentricity (unitless) + real (SHR_KIND_R8),intent(in) :: eccen ! eccentricity (unitless) ! (typically 0 to 0.1) - real (SHR_KIND_R8),intent(in) :: obliq ! obliquity (-90 to +90 degrees) + real (SHR_KIND_R8),intent(in) :: obliq ! obliquity (-90 to +90 degrees) ! typically 22-26 real (SHR_KIND_R8),intent(in) :: mvelp ! moving vernal equinox at perhel ! (0 to 360 degrees) @@ -770,7 +769,7 @@ SUBROUTINE shr_orb_print( iyear_AD, eccen, obliq, mvelp ) character(len=*),parameter :: F02 = "('(shr_orb_print) ',a,f6.3)" character(len=*),parameter :: F03 = "('(shr_orb_print) ',a,es14.6)" !---------------------------------------------------------------------------- - + if (s_loglev > 0) then if ( iyear_AD .ne. SHR_ORB_UNDEF_INT ) then if ( iyear_AD > 0 ) then @@ -787,7 +786,7 @@ SUBROUTINE shr_orb_print( iyear_AD, eccen, obliq, mvelp ) write(s_logunit,F03) 'Orbit parameters not set!' end if endif - + END SUBROUTINE shr_orb_print !=============================================================================== From cef70ebc0eebfdd79a53efe630c8e314c2d117cf Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 6 May 2016 10:16:27 -0600 Subject: [PATCH 53/61] Back out changes related to vertical_gradient_calculator_continuous We have decided not to use this version of the vertical gradient calculator Test suite: cime unit tests Test baseline: N/A Test namelist changes: N/A Test status: pass Fixes: None User interface changes?: No Code review: None --- driver_cpl/driver/CMakeLists.txt | 1 - driver_cpl/driver/prep_glc_mod.F90 | 8 +- ...ertical_gradient_calculator_continuous.F90 | 665 ------------------ .../vertical_gradient_calculator_factory.F90 | 50 -- .../CMakeLists.txt | 22 +- ...vertical_gradient_calculator_continuous.pf | 465 ------------ ...vertical_gradient_calculator_continuous.pf | 426 ----------- ..._gradient_calculator_continuousNoLimit.F90 | 55 -- share/csm_share/shr/CMakeLists.txt | 18 +- share/csm_share/shr/shr_matrix_mod.F90 | 145 ---- share/csm_share/test/unit/CMakeLists.txt | 2 - .../test/unit/shr_matrix_test/CMakeLists.txt | 20 - .../test_tridiagonal_inverse.pf | 88 --- 13 files changed, 7 insertions(+), 1958 deletions(-) delete mode 100644 driver_cpl/driver/vertical_gradient_calculator_continuous.F90 delete mode 100644 driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf delete mode 100644 driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf delete mode 100644 driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_calculator_continuousNoLimit.F90 delete mode 100644 share/csm_share/shr/shr_matrix_mod.F90 delete mode 100644 share/csm_share/test/unit/shr_matrix_test/CMakeLists.txt delete mode 100644 share/csm_share/test/unit/shr_matrix_test/test_tridiagonal_inverse.pf diff --git a/driver_cpl/driver/CMakeLists.txt b/driver_cpl/driver/CMakeLists.txt index 4b182fa1f2d..2767c0ea22e 100644 --- a/driver_cpl/driver/CMakeLists.txt +++ b/driver_cpl/driver/CMakeLists.txt @@ -6,7 +6,6 @@ list(APPEND drv_sources seq_map_type_mod.F90 vertical_gradient_calculator_base.F90 vertical_gradient_calculator_2nd_order.F90 - vertical_gradient_calculator_continuous.F90 vertical_gradient_calculator_factory.F90 ) diff --git a/driver_cpl/driver/prep_glc_mod.F90 b/driver_cpl/driver/prep_glc_mod.F90 index 496261d90b7..0ea2c121a5c 100644 --- a/driver_cpl/driver/prep_glc_mod.F90 +++ b/driver_cpl/driver/prep_glc_mod.F90 @@ -406,7 +406,7 @@ subroutine prep_glc_map_one_field_lnd2glc(egi, eli, fieldname, fractions_lx, map ! Note that we remap each field separately because each field needs its own ! vertical gradient calculator. - use vertical_gradient_calculator_continuous, only : vertical_gradient_calculator_continuous_type + use vertical_gradient_calculator_2nd_order, only : vertical_gradient_calculator_2nd_order_type use vertical_gradient_calculator_factory use glc_elevclass_mod, only : glc_get_num_elevation_classes, & glc_get_elevclass_bounds, glc_all_elevclass_strings @@ -421,12 +421,12 @@ subroutine prep_glc_map_one_field_lnd2glc(egi, eli, fieldname, fractions_lx, map ! ! Local Variables type(mct_avect), pointer :: g2x_gx - type(vertical_gradient_calculator_continuous_type) :: gradient_calculator + type(vertical_gradient_calculator_2nd_order_type) :: gradient_calculator !--------------------------------------------------------------- g2x_gx => component_get_c2x_cx(glc(egi)) - gradient_calculator = create_vertical_gradient_calculator_continuous( & + gradient_calculator = create_vertical_gradient_calculator_2nd_order( & attr_vect = l2gacc_lx(eli), & fieldname = fieldname, & toponame = 'Sl_topo', & @@ -439,8 +439,6 @@ subroutine prep_glc_map_one_field_lnd2glc(egi, eli, fieldname, fractions_lx, map gradient_calculator = gradient_calculator, & mapper = mapper, & l2x_g = l2x_gx(eli)) - ! FIXME(wjs, 2016-04-30) Remove this - call gradient_calculator%print_statistics() end subroutine prep_glc_map_one_field_lnd2glc diff --git a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 b/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 deleted file mode 100644 index e4469e5b772..00000000000 --- a/driver_cpl/driver/vertical_gradient_calculator_continuous.F90 +++ /dev/null @@ -1,665 +0,0 @@ -module vertical_gradient_calculator_continuous - - !--------------------------------------------------------------------- - ! - ! Purpose: - ! - ! This module defines a subclass of vertical_gradient_calculator_base_type for - ! computing piecewise continuous vertical gradients using a matrix solve. - -#include "shr_assert.h" - use seq_comm_mct, only : logunit - use vertical_gradient_calculator_base, only : vertical_gradient_calculator_base_type - use shr_kind_mod, only : r8 => shr_kind_r8 - use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) - use shr_log_mod, only : errMsg => shr_log_errMsg - use shr_sys_mod, only : shr_sys_abort - use shr_matrix_mod, only : tridiagonal_inverse - - implicit none - private - - public :: vertical_gradient_calculator_continuous_type - - type, extends(vertical_gradient_calculator_base_type) :: & - vertical_gradient_calculator_continuous_type - private - - integer :: nelev ! number of elevation classes - integer :: num_points - real(r8), allocatable :: field(:,:) ! field(i,j) is elevation class i, point j - real(r8), allocatable :: topo(:,:) ! topo(i,j) is elevation class i, point j - - ! precomputed vertical gradients; vertical_gradient(i,j) is elevation class i, field - ! j - real(r8), allocatable :: vertical_gradient(:,:) - - logical, allocatable :: topo_valid(:) ! whether topo is valid in each point - - ! Bounds of each elevation class. This array has one more element than the number of - ! elevation classes, since it contains lower and upper bounds for each elevation - ! class. The indices go 0:nelev. These bounds are guaranteed to be monotonically - ! increasing. - real(r8), allocatable :: elevclass_bounds(:) - - ! Calculator to determine initial guesses for gradients. We determine how good the - ! solution is based on how well we match these initial guesses. We also fall back on - ! these initial guesses if the gradient in a given elevation class is determined to - ! be 'bad'. - class(vertical_gradient_calculator_base_type), allocatable :: calculator_initial_guess - - logical :: calculated ! whether gradients have been calculated yet - - ! Various statistics for printing diagnostics. - logical, allocatable :: zeroed_from_topo_out_of_bounds(:) ! [num_points] - logical, allocatable :: limited_to_zero(:,:) ! [nelev, num_points] - logical, allocatable :: limited_to_initial_guess(:,:) ! [nelev, num_points] - - contains - procedure :: calc_gradients - procedure :: get_gradients_one_class - procedure :: get_gradients_one_point - procedure :: print_statistics - - ! This is public so that it can be overridden and/or tested independently by unit - ! tests - procedure :: limit_gradients - - procedure, private :: check_topo ! check topographic heights - procedure, private :: solve_for_vertical_gradients ! compute vertical gradients for all ECs, for points where we do a matrix solve - - procedure, private :: dl ! lower half-width of ec - procedure, private :: du ! upper half-width of ec - end type vertical_gradient_calculator_continuous_type - - interface vertical_gradient_calculator_continuous_type - module procedure constructor - end interface vertical_gradient_calculator_continuous_type - -contains - - !----------------------------------------------------------------------- - function constructor(field, topo, elevclass_bounds, calculator_initial_guess) & - result(this) - ! - ! !DESCRIPTION: - ! Creates a vertical_gradient_calculator_continuous_type object. - ! - ! Pre-condition: elevclass_bounds must be monotonically increasing. - ! - ! Pre-condition: Topographic heights should all lie inside the bounds of their - ! respective elevation class (given by elevclass_bounds), with the possible exception - ! of the lowest elevation class (topographic heights can lie below the arbitrary lower - ! bound of the elevation class) and the highest elevation class (topographic heights - ! can lie above the arbitrary upper bound of the elevation class). For grid cells - ! where this is not true, sets vertical gradient to 0 for all elevation classes. - ! - ! !USES: - ! - ! !ARGUMENTS: - type(vertical_gradient_calculator_continuous_type) :: this ! function result - real(r8), intent(in) :: field(:,:) ! field(i,j) is point i, elevation class j - real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j - - ! bounds of each elevation class; this array should have one more element than the - ! number of elevation classes, since it contains lower and upper bounds for each - ! elevation class - real(r8) , intent(in) :: elevclass_bounds(0:) - - ! Initial guesses for gradients. We determine how good the solution is based on how - ! well we match these initial guesses. We also fall back on these initial guesses if - ! the gradient in a given elevation class is determined to be 'bad'. - ! - ! The calc_gradients method should not yet have been called on this object - class(vertical_gradient_calculator_base_type), intent(in) :: calculator_initial_guess - ! - ! !LOCAL VARIABLES: - - character(len=*), parameter :: subname = 'constructor' - !----------------------------------------------------------------------- - - this%calculated = .false. - - this%num_points = size(field, 1) - this%nelev = size(field, 2) - SHR_ASSERT_ALL((ubound(topo) == (/this%num_points, this%nelev/)), errMsg(__FILE__, __LINE__)) - SHR_ASSERT_ALL((ubound(elevclass_bounds) == (/this%nelev/)), errMsg(__FILE__, __LINE__)) - - allocate(this%elevclass_bounds(0:this%nelev)) - this%elevclass_bounds(:) = elevclass_bounds(:) - call this%check_elevclass_bounds_monotonic_increasing(this%elevclass_bounds) - - allocate(this%field(this%nelev, this%num_points)) - this%field(:,:) = transpose(field(:,:)) - allocate(this%topo(this%nelev, this%num_points)) - this%topo(:,:) = transpose(topo(:,:)) - - allocate(this%topo_valid(this%num_points)) - call this%check_topo() - - allocate(this%vertical_gradient(this%nelev, this%num_points)) - this%vertical_gradient(:,:) = nan - - allocate(this%calculator_initial_guess, source = calculator_initial_guess) - - allocate(this%zeroed_from_topo_out_of_bounds(this%num_points)) - this%zeroed_from_topo_out_of_bounds(:) = .false. - allocate(this%limited_to_zero(this%nelev, this%num_points)) - this%limited_to_zero(:,:) = .false. - allocate(this%limited_to_initial_guess(this%nelev, this%num_points)) - this%limited_to_initial_guess(:,:) = .false. - - end function constructor - - !----------------------------------------------------------------------- - subroutine calc_gradients(this) - ! - ! !DESCRIPTION: - ! - ! - ! !USES: - ! - ! !ARGUMENTS: - class(vertical_gradient_calculator_continuous_type), intent(inout) :: this - ! - ! !LOCAL VARIABLES: - integer :: pt - - character(len=*), parameter :: subname = 'calc_gradients' - !----------------------------------------------------------------------- - - if (this%calculated) then - return - end if - - call this%calculator_initial_guess%calc_gradients() - do pt = 1, this%num_points - if (.not. this%topo_valid(pt)) then - this%vertical_gradient(:,pt) = 0._r8 - this%zeroed_from_topo_out_of_bounds(pt) = .true. - else - call this%solve_for_vertical_gradients(pt) - end if - end do - - this%calculated = .true. - - end subroutine calc_gradients - - - !----------------------------------------------------------------------- - subroutine get_gradients_one_class(this, elevation_class, gradients) - ! - ! !DESCRIPTION: - ! Returns the vertical gradient for all points, at a given elevation class. - ! - ! this%calc_gradients should already have been called - ! - ! !USES: - ! - ! !ARGUMENTS: - class(vertical_gradient_calculator_continuous_type), intent(in) :: this - integer, intent(in) :: elevation_class - - ! gradients should already be allocated to the appropriate size - real(r8), intent(out) :: gradients(:) - ! - ! !LOCAL VARIABLES: - - character(len=*), parameter :: subname = 'get_gradients_one_class' - !----------------------------------------------------------------------- - - SHR_ASSERT(this%calculated, errMsg(__FILE__, __LINE__)) - SHR_ASSERT((size(gradients) == this%num_points), errMsg(__FILE__, __LINE__)) - - if (elevation_class < 1 .or. & - elevation_class > this%nelev) then - write(logunit,*) subname, ': ERROR: elevation class out of bounds: ', & - elevation_class, this%nelev - call shr_sys_abort(subname//': ERROR: elevation class out of bounds') - end if - - gradients(:) = this%vertical_gradient(elevation_class, :) - - end subroutine get_gradients_one_class - - !----------------------------------------------------------------------- - subroutine get_gradients_one_point(this, point, gradients) - ! - ! !DESCRIPTION: - ! Returns the vertical gradient for all elevation classes, for one point - ! - ! this%calc_gradients should already have been called - ! - ! !USES: - ! - ! !ARGUMENTS: - class(vertical_gradient_calculator_continuous_type), intent(in) :: this - integer, intent(in) :: point - - ! gradients should already be allocated to the appropriate size - real(r8), intent(out) :: gradients(:) - ! - ! !LOCAL VARIABLES: - - character(len=*), parameter :: subname = 'get_gradients_one_point' - !----------------------------------------------------------------------- - - SHR_ASSERT(this%calculated, errMsg(__FILE__, __LINE__)) - SHR_ASSERT(point <= this%num_points, errMsg(__FILE__, __LINE__)) - SHR_ASSERT((size(gradients) == this%nelev), errMsg(__FILE__, __LINE__)) - - gradients(:) = this%vertical_gradient(:, point) - - end subroutine get_gradients_one_point - - !----------------------------------------------------------------------- - subroutine print_statistics(this) - ! - ! !DESCRIPTION: - ! Print various statistics on the solve to the logunit - ! - ! !USES: - ! - ! !ARGUMENTS: - class(vertical_gradient_calculator_continuous_type), intent(in) :: this - ! - ! !LOCAL VARIABLES: - integer :: ec - integer :: num_not_out_of_bounds - - character(len=*), parameter :: subname = 'print_statistics' - !----------------------------------------------------------------------- - - SHR_ASSERT(this%calculated, errMsg(__FILE__, __LINE__)) - - write(logunit, '(a)') "Vertical gradient calculator statistics: " - write(logunit, '(a, f10.6)') "Fraction with topo out of bounds: ", & - real(count(this%zeroed_from_topo_out_of_bounds), r8) / real(this%num_points, r8) - num_not_out_of_bounds = this%num_points - count(this%zeroed_from_topo_out_of_bounds) - do ec = 1, this%nelev - write(logunit, '(a, i4, f10.6)') "Remaining fraction limited to 0: ", ec, & - real(count(this%limited_to_zero(ec,:)), r8) / real(num_not_out_of_bounds, r8) - write(logunit, '(a, i4, f10.6)') "Remaining fraction limited to initial guess: ", ec, & - real(count(this%limited_to_initial_guess(ec,:)), r8) / real(num_not_out_of_bounds, r8) - end do - - end subroutine print_statistics - - - !----------------------------------------------------------------------- - subroutine check_topo(this) - ! - ! !DESCRIPTION: - ! Check topographic heights; set this%topo_valid(i) to false if there is a problem in - ! point i. - ! - ! Topographic heights in the attribute vector must all lie inside the bounds of their - ! respective elevation class (given by elevclass_bounds), with the possible exception - ! of the lowest elevation class (topographic heights can lie below the arbitrary lower - ! bound of the elevation class) and the highest elevation class (topographic heights - ! can lie above the arbitrary upper bound of the elevation class) - ! - ! !USES: - ! - ! !ARGUMENTS: - class(vertical_gradient_calculator_continuous_type), intent(inout) :: this - ! - ! !LOCAL VARIABLES: - integer :: elevclass - integer :: i - - ! Absolute tolerance for error checks. This is chosen so that it allows for - ! double-precision roundoff-level errors on values of order 10,000. - real(r8), parameter :: tol = 1.e-10_r8 - - character(len=*), parameter :: subname = 'check_topo' - !----------------------------------------------------------------------- - - this%topo_valid(:) = .true. - - do i = 1, this%num_points - do elevclass = 1, this%nelev - if (elevclass > 1) then - if (this%topo(elevclass,i) - this%elevclass_bounds(elevclass-1) < -tol) then - this%topo_valid(i) = .false. - end if - end if - - if (elevclass < this%nelev) then - if (this%topo(elevclass,i) - this%elevclass_bounds(elevclass) > tol) then - this%topo_valid(i) = .false. - end if - end if - end do - end do - - end subroutine check_topo - - !----------------------------------------------------------------------- - subroutine solve_for_vertical_gradients(this, pt) - ! - ! !DESCRIPTION: - ! Compute and save vertical gradients for all elevation classes in this point. - ! - ! This should only be called for points where we have done some initial checks to - ! show that we should attempt a matrix solve there. - ! - ! Computes a gradient in each elevation class such that the field is continuous at - ! interfaces and the sum over squared differences from the mean is minimized. - ! - ! !USES: - ! - ! !ARGUMENTS: - class(vertical_gradient_calculator_continuous_type), intent(inout) :: this - integer, intent(in) :: pt ! point to compute gradients for (1..this%num_points) - ! - ! !LOCAL VARIABLES: - real(r8) :: field(this%nelev) ! mean field value of each elevation class - real(r8) :: topo(this%nelev) ! mean topo of each elevation class - real(r8) :: grad(this%nelev) ! computed gradient - real(r8) :: grad_initial_guess(this%nelev) ! initial guess for gradient - real(r8) :: topo_interface(0:this%nelev) ! elevations at interfaces between classes - real(r8) :: h_lo(this%nelev) ! lower bounds for computing norms - real(r8) :: h_hi(this%nelev) ! upper bounds for computing norms - real(r8) :: dgrad(this%nelev) ! grad - grad_initial_guess - real(r8) :: weight_grad(this%nelev) ! weight for dgrad in solution - - real(r8) :: diag(this%nelev-1) ! diagonal of tridiagonal matrix - real(r8) :: subd(this%nelev-1) ! subdiagonal of tridiagonal matrix - real(r8) :: supd(this%nelev-1) ! superdiagonal of tridiagonal matrix - - real(r8) :: b(this%nelev-1) ! rhs in A*x = b - real(r8) :: A(this%nelev-1, this%nelev) ! matrix in A*x = b - real(r8) :: A_AT(this%nelev-1, this%nelev-1) ! A * (transpose of A) - real(r8) :: Tinv(this%nelev-1, this%nelev-1) ! inverse of tridiagonal matrix T = A * AT - real(r8) :: AT_Tinv(this%nelev, this%nelev-1) ! (transpose of A) * Tinv - real(r8) :: x_least_norm(this%nelev) ! least-norm solution x in A*x = b - - ! FIXME(wjs, 2016-04-27) Rename to nelev, or probably just delete - integer :: n - - ! FIXME(wjs, 2016-04-26) Rename to ec - integer :: i - - - character(len=*), parameter :: subname = 'solve_for_vertical_gradients' - !----------------------------------------------------------------------- - - field(:) = this%field(:,pt) - topo(:) = this%topo(:,pt) - - ! FIXME(wjs, 2016-04-27) Rename to elevclass_bounds? Update: just delete this - ! temporary variable. - topo_interface(:) = this%elevclass_bounds(:) - - n = this%nelev - - ! FIXME(wjs, 2016-04-26) Extract method for the following two loops: returns - ! weight_grad in each elevation class - do i = 1, n - - if (i == 1) then - ! If topo(1) is near the top of EC1, then the weight just includes twice the - ! width of [topo(i) .. topo_interface(i)] - h_lo(i) = max(topo_interface(i-1), (topo(i) - (topo_interface(i) - topo(i)))) - else - h_lo(i) = topo_interface(i-1) - end if - - if (i == n) then - ! If topo(n) is near the bottom of EC N, then the weight just includes twice - ! the width of [topo_interface(i-1) .. topo(i)] - h_hi(i) = min(topo_interface(i), (topo(i) + (topo(i) - topo_interface(i-1)))) - else - h_hi(i) = topo_interface(i) - end if - - end do - - do i = 1, n - ! set gradient weights based on h_hi - h_lo in each class - weight_grad(i) = (h_hi(i) - h_lo(i)) / (h_hi(n) - h_lo(1)) - - ! FIXME(wjs, 2016-04-26) Check that weight_grad > 0. - ! If weight_grad is just slightly > 0, the matrix will be poorly conditioned. - - end do - - call this%calculator_initial_guess%get_gradients_one_point(pt, grad_initial_guess) - - !-------------------------------------------------------------------- - ! Set up matrix problem for gradient solution. - ! The idea is to match field values at interfaces. - ! - ! For each class n: - ! field(n) + du(n)*grad(n) = field(n+1) - dl(n+1)*grad(n+1) - ! - ! Rearrange to get - ! du(n)*grad(n) + dl(n+1)*grad(n+1) = field(n+1) - field(n) - ! - ! This is a bidiagonal matrix system: - ! - ! | du(1) dl(2) | | grad(1) | | field(2) - field(1) | - ! | du(2) dl(3) | | grad(2) | | field(3) - field(2) | - ! | du(3) dl(4) | * | grad(3) | = | field(4) - field(3) | - ! | du(4) dl(5) | | grad(4) | | field(5) - field(4) | - ! | grad(5) | - ! - ! The solution is underdetermined (4 equations, 5 unknowns). - ! So we add an additional constraint: - ! Minimize the norm of the difference between the gradient in each class and the - ! mean gradient, weighted by the range of the elevation class. - ! That is, minimize the sum over i of (wt(i) * (grad(i) - grad_initial_guess(i)))^2. - ! - ! The mean gradient is given by - ! - ! field(n) - field(1) - ! grad_mean = __________________ - ! topo(n) - topo(1) - ! - ! The weights are - ! - ! h_hi(i) - h_lo(i) - ! wt(i) = _________________ - ! h_hi(n) - h_lo(1) - ! - ! Putting in the weights and rearranging, we get - ! - ! | wt(1) * dgrad(1) | | field(2) - field(1) | | wt(1) * grad_mean | - ! | wt(2) * dgrad(2) | | field(3) - field(2) | | wt(2) * grad_mean | - ! A * | wt(3) * dgrad(3) | = | field(4) - field(3) | - A * | wt(3) * grad_mean | - ! | wt(4) * dgrad(4) | | field(5) - field(4) | | wt(4) * grad_mean | - ! | wt(5) * dgrad(5) | | wt(5) * grad_mean | - ! - ! where A is the bidiagonal matrix above, adjusted to include the gradient weights. - ! E.g., du(1) becomes du(1)/wt(1), etc. - ! This system is in the form A*x = b, where we want to solve for x. - ! - ! It can be shown that the least-norm solution of A*x = b is given by - ! - ! x = A^T (A*A^T)^{-1} b - ! - ! So given A, we need to compute (A*A^T), takes its inverse, premultiply by A^T, - ! and multiply the result by b. - ! Given the least-norm solution, it is straightforward to compute the gradients. - !-------------------------------------------------------------------- - - ! Fill A - ! A has (n-1) rows and n columns - - A(:,:) = 0._r8 - do i = 1, n-1 - A(i,i) = this%du(pt,i) / weight_grad(i) - A(i,i+1) = this%dl(pt,i+1) / weight_grad(i+1) - end do - - ! Compute A * A^T, a tridiagonal matrix of size (n-1) - - A_AT = matmul(A, transpose(A)) - - ! Compute tridiagonal entries of (A * A^T) - - do i = 1, n-1 - diag(i) = A_AT(i,i) ! diagonal - if (i < n-1) then - supd(i) = A_AT(i,i+1) ! superdiagonal - subd(i) = A_AT(i+1,i) ! subdiagonal - else - supd(i) = 0._r8 - subd(i) = 0._r8 - end if - end do - - ! Compute inverse of (A * A^T) - ! Tinv has size (n-1,n-1) - - call tridiagonal_inverse(diag, supd, subd, Tinv) - - ! Premultiply by A^T - ! A^T * Tinv has n rows and (n-1) columns - - AT_Tinv = matmul(transpose(A), Tinv) - - ! Compute the rhs vector b - ! Start with the field differences - - do i = 1, n-1 - b(i) = field(i+1) - field(i) - enddo - - ! Subtract (A * weight_grad*grad_initial_guess), a vector of size(n-1) - - b(:) = b(:) - matmul(A, weight_grad(:)*grad_initial_guess(:)) - - ! Multiply AT_Tinv by b to get the least-norm solution - ! b has size (n-1), x_least_norm has size n - - x_least_norm = matmul(AT_Tinv, b) - - ! Divide by the weighting factor to get dgrad - dgrad(:) = x_least_norm(:) / weight_grad(:) - - ! Add dgrad to the target gradient to get the total gradient - grad(:) = grad_initial_guess(:) + dgrad(:) - - ! Limit gradients - call this%limit_gradients( & - pt = pt, & - grad_initial_guess = grad_initial_guess, & - grad = grad) - - ! Finally, set class-level values - this%vertical_gradient(:,pt) = grad(:) - - end subroutine solve_for_vertical_gradients - - !----------------------------------------------------------------------- - subroutine limit_gradients(this, pt, grad_initial_guess, grad) - ! - ! !DESCRIPTION: - ! Limit the computed gradients for the given point - ! - ! !USES: - ! - ! !ARGUMENTS: - class(vertical_gradient_calculator_continuous_type), intent(inout) :: this - integer, intent(in) :: pt - - ! All of these arguments should have size this%nelev - - ! we'll back off to these initial guesses if there is a problem with grad in a given - ! elevation class - real(r8), intent(in) :: grad_initial_guess(:) - - ! upon input, grad contains the current gradient estimates; upon output, it is - ! modified to be limited - real(r8), intent(inout) :: grad(:) - ! - ! !LOCAL VARIABLES: - real(r8) :: field(this%nelev) ! mean field value of each elevation class - integer :: ec - real(r8) :: val_at_lb ! value at the lower bound interface of an elevation class - real(r8) :: val_at_ub ! value at the upper bound interface of an elevation class - logical :: val_at_lb_outside_bounds - logical :: val_at_ub_outside_bounds - - character(len=*), parameter :: subname = 'limit_gradients' - !----------------------------------------------------------------------- - - SHR_ASSERT_ALL((ubound(grad_initial_guess) == (/this%nelev/)), errMsg(__FILE__, __LINE__)) - SHR_ASSERT_ALL((ubound(grad) == (/this%nelev/)), errMsg(__FILE__, __LINE__)) - - field(:) = this%field(:,pt) - - ! Set gradient to 0 in lowest and highest elevation class - grad(1) = 0._r8 - this%limited_to_zero(1, pt) = .true. - grad(this%nelev) = 0._r8 - this%limited_to_zero(this%nelev, pt) = .true. - - do ec = 2, this%nelev - 1 - val_at_lb = field(ec) - (this%dl(pt,ec) * grad(ec)) - val_at_lb_outside_bounds = is_outside_bounds(val_at_lb, field(ec), field(ec-1)) - - val_at_ub = field(ec) + (this%du(pt,ec) * grad(ec)) - val_at_ub_outside_bounds = is_outside_bounds(val_at_ub, field(ec), field(ec+1)) - - if (val_at_lb_outside_bounds .or. val_at_ub_outside_bounds) then - grad(ec) = grad_initial_guess(ec) - this%limited_to_initial_guess(ec, pt) = .true. - end if - end do - - contains - pure logical function is_outside_bounds(val, bound1, bound2) - ! Returns true if val is outside the interval given by bound1 and bound2 - real(r8), intent(in) :: val, bound1, bound2 - - if (val < bound1 .and. val < bound2) then - is_outside_bounds = .true. - else if (val > bound1 .and. val > bound2) then - is_outside_bounds = .true. - else - is_outside_bounds = .false. - end if - end function is_outside_bounds - end subroutine limit_gradients - - !----------------------------------------------------------------------- - function dl(this, pt, ec) - ! - ! !DESCRIPTION: - ! Return lower half-width of elevation class ec in point pt - ! - ! !ARGUMENTS: - real(r8) :: dl ! function result - class(vertical_gradient_calculator_continuous_type), intent(in) :: this - integer, intent(in) :: pt - integer, intent(in) :: ec - !----------------------------------------------------------------------- - - dl = this%topo(ec, pt) - this%elevclass_bounds(ec - 1) - - end function dl - - - !----------------------------------------------------------------------- - function du(this, pt, ec) - ! - ! !DESCRIPTION: - ! Return upper half-width of elevation class ec in point pt - ! - ! !ARGUMENTS: - real(r8) :: du ! function result - class(vertical_gradient_calculator_continuous_type), intent(in) :: this - integer, intent(in) :: pt - integer, intent(in) :: ec - !----------------------------------------------------------------------- - - du = this%elevclass_bounds(ec) - this%topo(ec, pt) - - end function du - - -end module vertical_gradient_calculator_continuous diff --git a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 index a31411598df..22455ce66cc 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_factory.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_factory.F90 @@ -9,14 +9,12 @@ module vertical_gradient_calculator_factory use shr_kind_mod, only : r8 => shr_kind_r8 use shr_log_mod, only : errMsg => shr_log_errMsg use vertical_gradient_calculator_2nd_order, only : vertical_gradient_calculator_2nd_order_type - use vertical_gradient_calculator_continuous, only : vertical_gradient_calculator_continuous_type use mct_mod implicit none private public :: create_vertical_gradient_calculator_2nd_order - public :: create_vertical_gradient_calculator_continuous ! The following routines are public just to support unit testing, and shouldn't be ! called from production code @@ -67,54 +65,6 @@ function create_vertical_gradient_calculator_2nd_order( & end function create_vertical_gradient_calculator_2nd_order - !----------------------------------------------------------------------- - function create_vertical_gradient_calculator_continuous( & - attr_vect, fieldname, toponame, elevclass_names, elevclass_bounds) & - result(calculator) - ! - ! !DESCRIPTION: - ! Creates and returns a vertical_gradient_calculator_continuous_type object - ! - ! The attribute vector is assumed to have fields named fieldname // - ! elevclass_names(1), toponame // elevclass_names(1), etc. - ! - ! !USES: - ! - ! !ARGUMENTS: - type(vertical_gradient_calculator_continuous_type) :: calculator ! function result - type(mct_aVect) , intent(in) :: attr_vect ! attribute vector in which we can find the data - character(len=*) , intent(in) :: fieldname ! base name of the field of interest - character(len=*) , intent(in) :: toponame ! base name of the topographic field - character(len=*) , intent(in) :: elevclass_names(:) ! strings corresponding to each elevation class - ! bounds of each elevation class; this array should have one more element than the - ! number of elevation classes, since it contains lower and upper bounds for each - ! elevation class - real(r8) , intent(in) :: elevclass_bounds(0:) - ! - ! !LOCAL VARIABLES: - integer :: nelev - real(r8), allocatable :: field(:,:) - real(r8), allocatable :: topo(:,:) - type(vertical_gradient_calculator_2nd_order_type) :: calculator_initial_guess - - character(len=*), parameter :: subname = 'create_vertical_gradient_calculator_continuous' - !----------------------------------------------------------------------- - - nelev = size(elevclass_names) - SHR_ASSERT_ALL((ubound(elevclass_bounds) == (/nelev/)), errMsg(__FILE__, __LINE__)) - - call extract_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names, & - field, topo) - - calculator_initial_guess = vertical_gradient_calculator_2nd_order_type( & - field = field, topo = topo, elevclass_bounds = elevclass_bounds) - - calculator = vertical_gradient_calculator_continuous_type( & - field = field, topo = topo, elevclass_bounds = elevclass_bounds, & - calculator_initial_guess = calculator_initial_guess) - - end function create_vertical_gradient_calculator_continuous - !----------------------------------------------------------------------- subroutine extract_data_from_attr_vect(attr_vect, fieldname, toponame, elevclass_names, & field_extracted, topo_extracted) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt index 0ce11af416b..ca7810516d5 100644 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/CMakeLists.txt @@ -1,29 +1,9 @@ set (pfunit_sources test_vertical_gradient_calculator_2nd_order.pf - test_vertical_gradient_calculator_continuous.pf test_vertical_gradient_calculator_factory.pf ) -# Only include output_vertical_gradient_calculator_continuous.pf if the -# environment variable OUTPUT_VGCONT is set: The "tests" in here aren't true -# unit tests, but rather more like "functional unit tests" (printing output for -# various inputs, for later inspection). This environment variable mechanism is -# a kludge to prevent running these "tests" that do i/o most of the time, only -# including them when requested. -# -# However, this mechanism requires rerunning cmake if you set or unset this -# environment variable. -if (DEFINED ENV{OUTPUT_VGCONT}) - list(APPEND pfunit_sources - output_vertical_gradient_calculator_continuous.pf - ) -endif() - -set (extra_sources - vertical_gradient_calculator_continuousNoLimit.F90 - ) - create_pFUnit_test(vertical_gradient_calculator vertical_gradient_calculator_exe - "${pfunit_sources}" "${extra_sources}") + "${pfunit_sources}" "") target_link_libraries(vertical_gradient_calculator_exe ${DRV_UNIT_TEST_LIBS}) diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf deleted file mode 100644 index 3fc01001345..00000000000 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/output_vertical_gradient_calculator_continuous.pf +++ /dev/null @@ -1,465 +0,0 @@ -module output_vertical_gradient_calculator_continuous - - ! This module runs vertical_gradient_calculator_continuous in some integration-style - ! tests, printing out the results for each test case for later plotting. - ! - ! We don't actually do assertions on the results - they are just made available for - ! manual inspection later. However, we (ab)use pfunit to do this anyway. - - use pfunit_mod - use vertical_gradient_calculator_continuous - use vertical_gradient_calculator_2nd_order - use vertical_gradient_calculator_continuousNoLimit - use shr_kind_mod , only : r8 => shr_kind_r8 - - implicit none - - @TestCase - type, extends(TestCase) :: OutputVGCCont - contains - procedure :: setUp - procedure :: tearDown - procedure :: run_test - procedure :: write_output - end type OutputVGCCont - - real(r8), parameter :: tol = 1.e-13_r8 - - ! Standard elevation class bounds when running with 10 elevation classes - real(r8), parameter :: bounds_10ec(11) = & - [0._r8, 200._r8, 400._r8, 700._r8, 1000._r8, 1300._r8, & - 1600._r8, 2000._r8, 2500._r8, 3000._r8, 10000._r8] - -contains - - ! ======================================================================== - ! Test helpers - ! ======================================================================== - - subroutine setUp(this) - class(OutputVGCCont), intent(inout) :: this - end subroutine setUp - - subroutine tearDown(this) - class(OutputVGCCont), intent(inout) :: this - end subroutine tearDown - - subroutine run_test(this, name, elevclass_bounds, topo, data) - ! Set up and run a test with the given inputs, for a single point - ! - ! Also outputs results to files - ! - ! Does separate outputs for: - ! - initialguess - ! - unlimited - ! - limited - class(OutputVGCCont), intent(inout) :: this - character(len=*), intent(in) :: name - real(r8), intent(in) :: elevclass_bounds(:) - real(r8), intent(in) :: topo(:) - real(r8), intent(in) :: data(:) - - type(vertical_gradient_calculator_2nd_order_type) :: calculator_initial_guess - type(vgc_continuousNoLimit_type) :: calculator_unlimited - type(vertical_gradient_calculator_continuous_type) :: calculator_limited - integer :: nelev - real(r8) :: gradients(size(topo)) - ! ------------------------------------------------------------------------ - - nelev = size(topo) - @assertEqual(nelev, size(data)) - @assertEqual(nelev+1, size(elevclass_bounds)) - - calculator_initial_guess = vertical_gradient_calculator_2nd_order_type( & - field = reshape(data, [1, nelev]), & - topo = reshape(topo, [1, nelev]), & - elevclass_bounds = elevclass_bounds) - call calculator_initial_guess%calc_gradients() - call calculator_initial_guess%get_gradients_one_point(point = 1, gradients = gradients) - call this%write_output( & - name = trim(name) // '_1initialguess', & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - gradients = gradients) - - calculator_unlimited = vgc_continuousNoLimit_type( & - field = reshape(data, [1, nelev]), & - topo = reshape(topo, [1, nelev]), & - elevclass_bounds = elevclass_bounds, & - calculator_initial_guess = calculator_initial_guess) - call calculator_unlimited%calc_gradients() - call calculator_unlimited%get_gradients_one_point(point = 1, gradients = gradients) - call this%write_output( & - name = trim(name) // '_2unlimited', & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - gradients = gradients) - - calculator_limited = vertical_gradient_calculator_continuous_type( & - field = reshape(data, [1, nelev]), & - topo = reshape(topo, [1, nelev]), & - elevclass_bounds = elevclass_bounds, & - calculator_initial_guess = calculator_initial_guess) - call calculator_limited%calc_gradients() - call calculator_limited%get_gradients_one_point(point = 1, gradients = gradients) - call this%write_output( & - name = trim(name) // '_3limited', & - elevclass_bounds = elevclass_bounds, & - topo = topo, & - data = data, & - gradients = gradients) - - end subroutine run_test - - subroutine write_output(this, name, elevclass_bounds, topo, data, gradients) - class(OutputVGCCont), intent(inout) :: this - character(len=*), intent(in) :: name - real(r8), intent(in) :: elevclass_bounds(:) - real(r8), intent(in) :: topo(:) - real(r8), intent(in) :: data(:) - real(r8), intent(in) :: gradients(:) - - integer :: n_elev_classes - character(len=:), allocatable :: filename - character(len=32) :: bounds_format - character(len=32) :: data_format - - character(len=*), parameter :: filename_prefix = 'gradients_continuous_' - character(len=*), parameter :: filename_suffix = '.txt' - integer, parameter :: out_unit = 11 - - ! ------------------------------------------------------------------------ - - n_elev_classes = size(gradients) - @assertEqual(n_elev_classes + 1, size(elevclass_bounds)) - @assertEqual(n_elev_classes, size(topo)) - @assertEqual(n_elev_classes, size(data)) - - filename = filename_prefix // trim(name) // filename_suffix - open(out_unit, file=filename, action='write') - - write(bounds_format, '(a, i0, a, a)') '(', n_elev_classes + 1, 'f20.10', ')' - write(data_format, '(a, i0, a, a)') '(', n_elev_classes, 'f20.10', ')' - - write(out_unit, '(i0)') n_elev_classes - write(out_unit, bounds_format) elevclass_bounds - write(out_unit, data_format) topo - write(out_unit, data_format) data - write(out_unit, data_format) gradients - - close(out_unit) - end subroutine write_output - - ! ======================================================================== - ! Actual tests - ! ======================================================================== - - @Test - subroutine basic(this) - class(OutputVGCCont), intent(inout) :: this - - call this%run_test( & - name = 'basic', & - elevclass_bounds = [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8], & - topo = [10._r8, 30._r8, 50._r8, 70._r8, 90._r8], & - data = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8]) - end subroutine basic - - ! ------------------------------------------------------------------------ - ! The following are taken from a cpl hist file with date stamp 0002-01-01 from test - ! SMS_D_Ly1.f09_g16.TG.yellowstone_intel - ! - ! The name of the test gives the x_y coordinate - ! ------------------------------------------------------------------------ - - @Test - subroutine tg_year2_x237_y177(this) - class(OutputVGCCont), intent(inout) :: this - - call this%run_test( & - name = 'tg_year2_x237_y177', & - elevclass_bounds = bounds_10ec, & - topo = [ & - 100.00000000000000000_r8, & - 300.00000000000000000_r8, & - 525.87927246093750000_r8, & - 850.00000000000000000_r8, & - 1174.07202148437500000_r8, & - 1491.06066894531250000_r8, & - 1634.75134277343750000_r8, & - 2250.00000000000000000_r8, & - 2750.00000000000000000_r8, & - 3500.00000000000000000_r8], & - data = [ & - -0.00003017942981387_r8, & - 0.00000000000000000_r8, & - 0.00000517720582138_r8, & - 0.00001201005943585_r8, & - 0.00001159968905995_r8, & - 0.00001089518445951_r8, & - 0.00000945808187680_r8, & - 0.00000954108236328_r8, & - 0.00000874735724210_r8, & - 0.00000951678703132_r8] & - ) - end subroutine tg_year2_x237_y177 - - @Test - subroutine tg_year2_x254_y179(this) - class(OutputVGCCont), intent(inout) :: this - - call this%run_test( & - name = 'tg_year2_x254_y179', & - elevclass_bounds = bounds_10ec, & - topo = [ & - 100.00000000000000000_r8, & - 300.00000000000000000_r8, & - 550.00000000000000000_r8, & - 850.00000000000000000_r8, & - 1150.00000000000000000_r8, & - 1450.00000000000000000_r8, & - 1800.00000000000000000_r8, & - 2432.00463867187500000_r8, & - 2512.73315429687500000_r8, & - 3500.00000000000000000_r8], & - data = [ & - -0.00004915712270304_r8, & - -0.00003682274473249_r8, & - -0.00002054757533188_r8, & - 0.00000890800856723_r8, & - 0.00000912497216632_r8, & - 0.00000876096328284_r8, & - 0.00000813312544778_r8, & - 0.00000723849916540_r8, & - 0.00000716611339158_r8, & - 0.00000634025218460_r8] & - ) - end subroutine tg_year2_x254_y179 - - @Test - subroutine tg_year2_x269_y179(this) - class(OutputVGCCont), intent(inout) :: this - - call this%run_test( & - name = 'tg_year2_x269_y179', & - elevclass_bounds = bounds_10ec, & - topo = [ & - 100.00000000000000000_r8, & - 300.00000000000000000_r8, & - 673.48120117187500000_r8, & - 891.23760986328125000_r8, & - 1033.81237792968750000_r8, & - 1450.00000000000000000_r8, & - 1800.00000000000000000_r8, & - 2250.00000000000000000_r8, & - 2750.00000000000000000_r8, & - 3500.00000000000000000_r8], & - data = [ & - -0.00004158946831012_r8, & - -0.00002728389881668_r8, & - -0.00000371834676116_r8, & - -0.00000029612721164_r8, & - -0.00000001078467626_r8, & - -0.00000063053033728_r8, & - -0.00000042971075231_r8, & - -0.00000006648337347_r8, & - 0.00000000000000000_r8, & - 0.00001133324531111_r8] & - ) - end subroutine tg_year2_x269_y179 - - @Test - subroutine tg_year2_x245_y171(this) - class(OutputVGCCont), intent(inout) :: this - - call this%run_test( & - name = 'tg_year2_x245_y171', & - elevclass_bounds = bounds_10ec, & - topo = [ & - 100.00000000000000000_r8, & - 300.00000000000000000_r8, & - 550.00000000000000000_r8, & - 850.00000000000000000_r8, & - 1150.00000000000000000_r8, & - 1450.00000000000000000_r8, & - 1800.00000000000000000_r8, & - 2250.00000000000000000_r8, & - 2750.00000000000000000_r8, & - 3500.00000000000000000_r8], & - data = [ & - -0.00006216453766683_r8, & - -0.00005053480708739_r8, & - -0.00003879935684381_r8, & - -0.00002813775790855_r8, & - -0.00000682415202391_r8, & - 0.00000000000000000_r8, & - 0.00000000000000000_r8, & - 0.00000581615859119_r8, & - 0.00000578505614612_r8, & - 0.00000704487820258_r8] & - ) - end subroutine tg_year2_x245_y171 - - @Test - subroutine tg_year2_x253_y171(this) - class(OutputVGCCont), intent(inout) :: this - - call this%run_test( & - name = 'tg_year2_x253_y171', & - elevclass_bounds = bounds_10ec, & - topo = [ & - 100.00000000000000000_r8, & - 300.00000000000000000_r8, & - 550.00000000000000000_r8, & - 850.00000000000000000_r8, & - 1150.00000000000000000_r8, & - 1450.00000000000000000_r8, & - 1800.00000000000000000_r8, & - 2250.00000000000000000_r8, & - 2741.18652343750000000_r8, & - 3500.00000000000000000_r8], & - data = [ & - -0.00005745654925704_r8, & - -0.00004694490053225_r8, & - -0.00003445266702329_r8, & - 0.00000570591555515_r8, & - 0.00001267663446924_r8, & - 0.00001288491057494_r8, & - 0.00001228994733538_r8, & - 0.00001145447004092_r8, & - 0.00001063432864612_r8, & - 0.00000983382778941_r8] & - ) - end subroutine tg_year2_x253_y171 - - @Test - subroutine tg_year2_x266_y173(this) - class(OutputVGCCont), intent(inout) :: this - - call this%run_test( & - name = 'tg_year2_x266_y173', & - elevclass_bounds = bounds_10ec, & - topo = [ & - 100.00000000000000000_r8, & - 300.00000000000000000_r8, & - 550.00000000000000000_r8, & - 871.65301513671875000_r8, & - 1211.49768066406250000_r8, & - 1432.28137207031250000_r8, & - 1665.71423339843750000_r8, & - 2250.00000000000000000_r8, & - 2750.00000000000000000_r8, & - 3500.00000000000000000_r8], & - data = [ & - -0.00006089130329201_r8, & - -0.00005163572859601_r8, & - -0.00004160191747360_r8, & - -0.00002927129935415_r8, & - -0.00001998909647227_r8, & - -0.00001512261678727_r8, & - -0.00001263937429030_r8, & - -0.00000406959406973_r8, & - 0.00000000000000000_r8, & - 0.00001004394562187_r8] & - ) - end subroutine tg_year2_x266_y173 - - @Test - subroutine tg_year2_x247_y164(this) - class(OutputVGCCont), intent(inout) :: this - - call this%run_test( & - name = 'tg_year2_x247_y164', & - elevclass_bounds = bounds_10ec, & - topo = [ & - 100.00000000000000000_r8, & - 300.00000000000000000_r8, & - 550.00000000000000000_r8, & - 850.00000000000000000_r8, & - 1150.00000000000000000_r8, & - 1450.00000000000000000_r8, & - 1800.00000000000000000_r8, & - 2250.00000000000000000_r8, & - 2750.00000000000000000_r8, & - 3500.00000000000000000_r8], & - data = [ & - -0.00008932914352044_r8, & - -0.00007648371683899_r8, & - -0.00006422778824344_r8, & - -0.00004912317308481_r8, & - -0.00003472463868093_r8, & - -0.00000160152967510_r8, & - 0.00000921983701119_r8, & - 0.00000909015761863_r8, & - 0.00001246667034138_r8, & - 0.00001318262184213_r8] & - ) - end subroutine tg_year2_x247_y164 - - @Test - subroutine tg_year2_x251_y164(this) - class(OutputVGCCont), intent(inout) :: this - - call this%run_test( & - name = 'tg_year2_x251_y164', & - elevclass_bounds = bounds_10ec, & - topo = [ & - 100.00000000000000000_r8, & - 300.00000000000000000_r8, & - 550.00000000000000000_r8, & - 850.00000000000000000_r8, & - 1150.00000000000000000_r8, & - 1450.00000000000000000_r8, & - 1800.00000000000000000_r8, & - 2441.64257812500000000_r8, & - 2564.37524414062500000_r8, & - 3500.00000000000000000_r8], & - data = [ & - -0.00010203790589003_r8, & - -0.00008802362572169_r8, & - -0.00006545173528139_r8, & - -0.00003821765130851_r8, & - -0.00001615641485841_r8, & - 0.00000000000000000_r8, & - 0.00001520295427326_r8, & - 0.00001540742596262_r8, & - 0.00001525775314803_r8, & - 0.00001418547435605_r8] & - ) - end subroutine tg_year2_x251_y164 - - @Test - subroutine tg_year2_x255_y163(this) - class(OutputVGCCont), intent(inout) :: this - - call this%run_test( & - name = 'tg_year2_x255_y163', & - elevclass_bounds = bounds_10ec, & - topo = [ & - 100.00000000000000000_r8, & - 277.44519042968750000_r8, & - 561.45306396484375000_r8, & - 830.48663330078125000_r8, & - 1167.21862792968750000_r8, & - 1373.33569335937500000_r8, & - 1800.00000000000000000_r8, & - 2250.00000000000000000_r8, & - 2750.00000000000000000_r8, & - 3500.00000000000000000_r8], & - data = [ & - -0.00002278820284118_r8, & - -0.00000824440030556_r8, & - 0.00000927457949729_r8, & - 0.00002404907900200_r8, & - 0.00003126181036350_r8, & - 0.00003124762952211_r8, & - 0.00003117840242339_r8, & - 0.00003535178620950_r8, & - 0.00003866513725370_r8, & - 0.00004323109533289_r8] & - ) - end subroutine tg_year2_x255_y163 - -end module output_vertical_gradient_calculator_continuous diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf b/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf deleted file mode 100644 index 05b2b7fc78c..00000000000 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/test_vertical_gradient_calculator_continuous.pf +++ /dev/null @@ -1,426 +0,0 @@ -module test_vertical_gradient_calculator_continuous - - ! Tests of vertical_gradient_calculator_continuous - - use pfunit_mod - use vertical_gradient_calculator_continuous - use vertical_gradient_calculator_continuousNoLimit - use vertical_gradient_calculator_specified - use shr_kind_mod , only : r8 => shr_kind_r8 - - implicit none - save - - @TestCase - type, extends(TestCase) :: TestVertGradCalcCont - contains - procedure :: setUp - procedure :: tearDown - procedure :: create_calculator - procedure :: create_calculator_one_point - procedure :: create_calculatorNoLimit - procedure :: create_calculatorNoLimit_one_point - end type TestVertGradCalcCont - - real(r8), parameter :: tol = 1.e-13_r8 - -contains - - subroutine setUp(this) - class(TestVertGradCalcCont), intent(inout) :: this - end subroutine setUp - - subroutine tearDown(this) - class(TestVertGradCalcCont), intent(inout) :: this - end subroutine tearDown - - function create_calculator(this, topo, data, elevclass_bounds) & - result(calculator) - type(vertical_gradient_calculator_continuous_type) :: calculator - class(TestVertGradCalcCont), intent(inout) :: this - real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j - real(r8), intent(in) :: data(:,:) ! data(i,j) is point i, elevation class j - - ! bounds of each elevation class; this array should have one more element than the - ! number of elevation classes, since it contains lower and upper bounds for each - ! elevation class - real(r8), intent(in) :: elevclass_bounds(:) - - integer :: n_elev_classes - - n_elev_classes = size(data,2) - @assertEqual(size(data), size(topo)) - @assertEqual(n_elev_classes + 1, size(elevclass_bounds)) - - calculator = vertical_gradient_calculator_continuous_type( & - field = data, & - topo = topo, & - elevclass_bounds = elevclass_bounds, & - calculator_initial_guess = vgc_specified_mean_slope(data, topo)) - call calculator%calc_gradients() - - end function create_calculator - - function create_calculator_one_point(this, topo, data, elevclass_bounds) & - result(calculator) - ! Convenience wrapper to create_calculator, when just dealing with one point - type(vertical_gradient_calculator_continuous_type) :: calculator - class(TestVertGradCalcCont), intent(inout) :: this - real(r8), intent(in) :: topo(:) - real(r8), intent(in) :: data(:) - - ! bounds of each elevation class; this array should have one more element than the - ! number of elevation classes, since it contains lower and upper bounds for each - ! elevation class - real(r8), intent(in) :: elevclass_bounds(:) - - - calculator = this%create_calculator( & - topo = reshape(topo, [1, size(topo)]), & - data = reshape(data, [1, size(data)]), & - elevclass_bounds = elevclass_bounds) - end function create_calculator_one_point - - function create_calculatorNoLimit(this, topo, data, elevclass_bounds) & - result(calculator) - type(vgc_continuousNoLimit_type) :: calculator - class(TestVertGradCalcCont), intent(inout) :: this - real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j - real(r8), intent(in) :: data(:,:) ! data(i,j) is point i, elevation class j - - ! bounds of each elevation class; this array should have one more element than the - ! number of elevation classes, since it contains lower and upper bounds for each - ! elevation class - real(r8), intent(in) :: elevclass_bounds(:) - - integer :: n_elev_classes - - n_elev_classes = size(data,2) - @assertEqual(size(data), size(topo)) - @assertEqual(n_elev_classes + 1, size(elevclass_bounds)) - - calculator = vgc_continuousNoLimit_type( & - field = data, & - topo = topo, & - elevclass_bounds = elevclass_bounds, & - calculator_initial_guess = vgc_specified_mean_slope(data, topo)) - call calculator%calc_gradients() - - end function create_calculatorNoLimit - - function create_calculatorNoLimit_one_point(this, topo, data, elevclass_bounds) & - result(calculator) - ! Convenience wrapper to create_calculator, when just dealing with one point - type(vgc_continuousNoLimit_type) :: calculator - class(TestVertGradCalcCont), intent(inout) :: this - real(r8), intent(in) :: topo(:) - real(r8), intent(in) :: data(:) - - ! bounds of each elevation class; this array should have one more element than the - ! number of elevation classes, since it contains lower and upper bounds for each - ! elevation class - real(r8), intent(in) :: elevclass_bounds(:) - - - calculator = this%create_calculatorNoLimit( & - topo = reshape(topo, [1, size(topo)]), & - data = reshape(data, [1, size(data)]), & - elevclass_bounds = elevclass_bounds) - end function create_calculatorNoLimit_one_point - - @Test - subroutine basic(this) - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(6) = & - [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8] - real(r8), parameter :: topo(5) = [10._r8, 30._r8, 50._r8, 70._r8, 90._r8] - real(r8), parameter :: data(5) = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8] - real(r8) :: gradients(5) - real(r8) :: expected_gradients(5) - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%get_gradients_one_point(point = 1, gradients = gradients) - - ! Note that gradient is set to 0 in lowest EC - expected_gradients(:) = [0._r8, 0.15_r8, 0.10_r8, 0.05_r8, 0.0_r8] - @assertEqual(expected_gradients, gradients, tolerance=tol) - - end subroutine basic - - @Test - subroutine basic_noLimit(this) - ! Ensure that gradients are computed as expected before applying the limiter - class(TestVertGradCalcCont), intent(inout) :: this - type(vgc_continuousNoLimit_type) :: calculator - real(r8), parameter :: elevclass_bounds(6) = & - [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8] - real(r8), parameter :: topo(5) = [10._r8, 30._r8, 50._r8, 70._r8, 90._r8] - real(r8), parameter :: data(5) = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8] - real(r8) :: gradients(5) - real(r8) :: expected_gradients(5) - - calculator = this%create_calculatorNoLimit_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%get_gradients_one_point(point = 1, gradients = gradients) - - expected_gradients(:) = [0.2_r8, 0.15_r8, 0.10_r8, 0.05_r8, 0.0_r8] - @assertEqual(expected_gradients, gradients, tolerance=tol) - - end subroutine basic_noLimit - - @Test - subroutine topo_outOfBoundsHigh(this) - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(6) = & - [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8] - real(r8), parameter :: topo(5) = [10._r8, 40._r8 + 1.e-5_r8, 50._r8, 70._r8, 90._r8] - real(r8), parameter :: data(5) = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8] - real(r8) :: gradients(5) - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%get_gradients_one_point(point = 1, gradients = gradients) - - @assertEqual([0._r8, 0._r8, 0._r8, 0._r8, 0._r8], gradients) - end subroutine topo_outOfBoundsHigh - - @Test - subroutine topo_outOfBoundsLow(this) - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(6) = & - [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8] - real(r8), parameter :: topo(5) = [10._r8, 20._r8 - 1.e-5_r8, 50._r8, 70._r8, 90._r8] - real(r8), parameter :: data(5) = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8] - real(r8) :: gradients(5) - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%get_gradients_one_point(point = 1, gradients = gradients) - - @assertEqual([0._r8, 0._r8, 0._r8, 0._r8, 0._r8], gradients) - end subroutine topo_outOfBoundsLow - - ! ------------------------------------------------------------------------ - ! Tests of limit_gradients - ! - ! For many of the tests above, we stubbed out limit_gradients, so we test that routine - ! separately here. - ! ------------------------------------------------------------------------ - - @Test - subroutine limitGradients_0InFirstEC(this) - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(6) = & - [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8] - real(r8), parameter :: topo(5) = [10._r8, 30._r8, 50._r8, 70._r8, 90._r8] - real(r8), parameter :: data(5) = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8] - real(r8) :: grad(5) = [1._r8, 2._r8, 3._r8, 4._r8, 5._r8] - real(r8) :: grad_initial_guess(5) - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - grad_initial_guess = grad - call calculator%limit_gradients(1, grad_initial_guess, grad) - - @assertEqual(0._r8, grad(1)) - end subroutine limitGradients_0InFirstEC - - @Test - subroutine limitGradients_0InLastEC(this) - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(6) = & - [0._r8, 20._r8, 40._r8, 60._r8, 80._r8, 100._r8] - real(r8), parameter :: topo(5) = [10._r8, 30._r8, 50._r8, 70._r8, 90._r8] - real(r8), parameter :: data(5) = [2._r8, 5.5_r8, 8._r8, 9.5_r8, 10._r8] - real(r8) :: grad(5) = [1._r8, 2._r8, 3._r8, 4._r8, 5._r8] - real(r8) :: grad_initial_guess(5) - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - grad_initial_guess = grad - call calculator%limit_gradients(1, grad_initial_guess, grad) - - @assertEqual(0._r8, grad(5)) - end subroutine limitGradients_0InLastEC - - @Test - subroutine limitGradients_almostLimitedPositiveLB(this) - ! Make sure that a positive gradient that should *almost* (but not quite) be limited - ! by the limiter (due to the lower bound) isn't limited. - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] - real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] - real(r8), parameter :: data(3) = [11._r8, 12._r8, 100._r8] - real(r8), parameter :: grad_ec2 = 0.039999_r8 - real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] - real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%limit_gradients(1, grad_initial_guess, grad) - - @assertEqual(grad_ec2, grad(2)) - end subroutine limitGradients_almostLimitedPositiveLB - - @Test - subroutine limitGradients_almostLimitedPositiveUB(this) - ! Make sure that a positive gradient that should *almost* (but not quite) be limited - ! by the limiter (due to the upper bound) isn't limited. - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] - real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] - real(r8), parameter :: data(3) = [0._r8, 12._r8, 13._r8] - real(r8), parameter :: grad_ec2 = 0.013332_r8 - real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] - real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%limit_gradients(1, grad_initial_guess, grad) - - @assertEqual(grad_ec2, grad(2)) - end subroutine limitGradients_almostLimitedPositiveUB - - @Test - subroutine limitGradients_almostLimitedNegativeLB(this) - ! Make sure that a negative gradient that should *almost* (but not quite) be limited - ! by the limiter (due to the lower bound) isn't limited. - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] - real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] - real(r8), parameter :: data(3) = [13._r8, 12._r8, 0._r8] - real(r8), parameter :: grad_ec2 = -0.039999_r8 - real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] - real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%limit_gradients(1, grad_initial_guess, grad) - - @assertEqual(grad_ec2, grad(2)) - end subroutine limitGradients_almostLimitedNegativeLB - - @Test - subroutine limitGradients_almostLimitedNegativeUB(this) - ! Make sure that a negative gradient that should *almost* (but not quite) be limited - ! by the limiter (due to the upper bound) isn't limited. - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] - real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] - real(r8), parameter :: data(3) = [100._r8, 12._r8, 11._r8] - real(r8), parameter :: grad_ec2 = -0.013332_r8 - real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] - real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%limit_gradients(1, grad_initial_guess, grad) - - @assertEqual(grad_ec2, grad(2)) - end subroutine limitGradients_almostLimitedNegativeUB - - @Test - subroutine limitGradients_limitedPositiveLB(this) - ! Make sure that a positive gradient that should be limited by the limiter (due to the - ! lower bound) is in fact limited. - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] - real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] - real(r8), parameter :: data(3) = [11._r8, 12._r8, 100._r8] - real(r8), parameter :: grad_ec2 = 0.04001_r8 - real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] - real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%limit_gradients(1, grad_initial_guess, grad) - - @assertEqual(grad_initial_guess(2), grad(2)) - end subroutine limitGradients_limitedPositiveLB - - @Test - subroutine limitGradients_limitedPositiveUB(this) - ! Make sure that a positive gradient that should be limited by the limiter (due to the - ! upper bound) is in fact limited. - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] - real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] - real(r8), parameter :: data(3) = [0._r8, 12._r8, 13._r8] - real(r8), parameter :: grad_ec2 = 0.013334_r8 - real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] - real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%limit_gradients(1, grad_initial_guess, grad) - - @assertEqual(grad_initial_guess(2), grad(2)) - end subroutine limitGradients_limitedPositiveUB - - @Test - subroutine limitGradients_limitedNegativeLB(this) - ! Make sure that a negative gradient that should be limited by the limiter (due to the - ! lower bound) is in fact limited. - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] - real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] - real(r8), parameter :: data(3) = [13._r8, 12._r8, 0._r8] - real(r8), parameter :: grad_ec2 = -0.04001_r8 - real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] - real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%limit_gradients(1, grad_initial_guess, grad) - - @assertEqual(grad_initial_guess(2), grad(2)) - end subroutine limitGradients_limitedNegativeLB - - @Test - subroutine limitGradients_limitedNegativeUB(this) - ! Make sure that a negative gradient that should be limited by the limiter (due to the - ! upper bound) is in fact limited. - class(TestVertGradCalcCont), intent(inout) :: this - type(vertical_gradient_calculator_continuous_type) :: calculator - real(r8), parameter :: elevclass_bounds(4) = [0._r8, 100._r8, 200._r8, 300._r8] - real(r8), parameter :: topo(3) = [50._r8, 125._r8, 275._r8] - real(r8), parameter :: data(3) = [100._r8, 12._r8, 11._r8] - real(r8), parameter :: grad_ec2 = -0.013334_r8 - real(r8) :: grad(3) = [0._r8, grad_ec2, 0._r8] - real(r8) :: grad_initial_guess(3) = [0._r8, 17._r8, 0._r8] - - calculator = this%create_calculator_one_point(topo=topo, data=data, & - elevclass_bounds=elevclass_bounds) - - call calculator%limit_gradients(1, grad_initial_guess, grad) - - @assertEqual(grad_initial_guess(2), grad(2)) - end subroutine limitGradients_limitedNegativeUB - -end module test_vertical_gradient_calculator_continuous diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_calculator_continuousNoLimit.F90 b/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_calculator_continuousNoLimit.F90 deleted file mode 100644 index 7cbb4af20ea..00000000000 --- a/driver_cpl/unit_test/vertical_gradient_calculator_test/vertical_gradient_calculator_continuousNoLimit.F90 +++ /dev/null @@ -1,55 +0,0 @@ -module vertical_gradient_calculator_continuousNoLimit - ! This module provides a type that inherits from - ! vertical_gradient_calculator_continuous, overriding the limiting to result in no - ! limiting of the initially-computed gradients. - - use shr_kind_mod, only : r8 => shr_kind_r8 - use vertical_gradient_calculator_base, only : & - vertical_gradient_calculator_base_type - use vertical_gradient_calculator_continuous, only : & - vertical_gradient_calculator_continuous_type - - implicit none - private - - public :: vgc_continuousNoLimit_type - - type, extends(vertical_gradient_calculator_continuous_type) :: & - vgc_continuousNoLimit_type - private - - contains - procedure :: limit_gradients - end type vgc_continuousNoLimit_type - - interface vgc_continuousNoLimit_type - module procedure constructor - end interface vgc_continuousNoLimit_type - -contains - - function constructor(field, topo, elevclass_bounds, calculator_initial_guess) result(this) - type(vgc_continuousNoLimit_type) :: this - real(r8), intent(in) :: field(:,:) ! field(i,j) is point i, elevation class j - real(r8), intent(in) :: topo(:,:) ! topo(i,j) is point i, elevation class j - ! bounds of each elevation class; this array should have one more element than the - ! number of elevation classes, since it contains lower and upper bounds for each - ! elevation class - real(r8) , intent(in) :: elevclass_bounds(0:) - class(vertical_gradient_calculator_base_type), intent(in) :: calculator_initial_guess - - this%vertical_gradient_calculator_continuous_type = & - vertical_gradient_calculator_continuous_type( & - field, topo, elevclass_bounds, calculator_initial_guess) - end function constructor - - subroutine limit_gradients(this, pt, grad_initial_guess, grad) - class(vgc_continuousNoLimit_type), intent(inout) :: this - integer, intent(in) :: pt - real(r8), intent(in) :: grad_initial_guess(:) - real(r8), intent(inout) :: grad(:) - - ! do nothing - end subroutine limit_gradients - -end module vertical_gradient_calculator_continuousNoLimit diff --git a/share/csm_share/shr/CMakeLists.txt b/share/csm_share/shr/CMakeLists.txt index ba9ec40f735..87b63287a56 100644 --- a/share/csm_share/shr/CMakeLists.txt +++ b/share/csm_share/shr/CMakeLists.txt @@ -7,21 +7,9 @@ sourcelist_to_parent(share_genf90_sources) list(APPEND share_sources "${share_genf90_sources}") -list(APPEND share_sources - shr_file_mod.F90 - shr_kind_mod.F90 - shr_const_mod.F90 - shr_sys_mod.F90 - shr_log_mod.F90 - shr_orb_mod.F90 - shr_spfn_mod.F90 - shr_strconvert_mod.F90 - shr_matrix_mod.F90 - shr_nl_mod.F90 - shr_precip_mod.F90 - shr_string_mod.F90 - shr_timer_mod.F90 - shr_vmath_mod.F90 +list(APPEND share_sources shr_file_mod.F90 shr_kind_mod.F90 shr_const_mod.F90 + shr_sys_mod.F90 shr_log_mod.F90 shr_orb_mod.F90 shr_spfn_mod.F90 shr_strconvert_mod.F90 + shr_nl_mod.F90 shr_precip_mod.F90 shr_string_mod.F90 shr_timer_mod.F90 shr_vmath_mod.F90 shr_wv_sat_mod.F90) # Build a separate list containing the mct wrapper and its dependencies. That diff --git a/share/csm_share/shr/shr_matrix_mod.F90 b/share/csm_share/shr/shr_matrix_mod.F90 deleted file mode 100644 index bdfcf35a97d..00000000000 --- a/share/csm_share/shr/shr_matrix_mod.F90 +++ /dev/null @@ -1,145 +0,0 @@ -module shr_matrix_mod - - ! This module contains routines for working with matrices. - - ! If possible, use routines from BLAS / LAPACK. This module should only contain - ! routines that go beyond what is available in BLAS / LAPACK. - -#include "shr_assert.h" - use shr_kind_mod, only : r8 => SHR_KIND_R8 - use shr_log_mod, only : errMsg => shr_log_errMsg - - implicit none - private - save - - public :: tridiagonal_inverse ! invert a tridiagonal matrix - -contains - - !----------------------------------------------------------------------- - subroutine tridiagonal_inverse(a, b, c, Tinv) - ! - ! !DESCRIPTION: - ! Inverts a tridiagonal matrix. - ! - ! All input / output arrays should have the same number of elements. - ! - ! Author: Bill Lipscomb - ! - ! !USES: - ! - ! !ARGUMENTS: - real(r8), intent(in) :: a(:) ! Center diagonal - real(r8), intent(in) :: b(:) ! Upper diagonal (superdiagonal); b(n) is ignored - real(r8), intent(in) :: c(:) ! Lower diagonal (subdiagonal); c(n) is ignored - real(r8), intent(out) :: Tinv(:,:) ! Inverse matrix - ! - ! !LOCAL VARIABLES: - integer :: n ! matrix dimension - integer :: i, j, ii - real(r8) :: theta(0:size(a)) - real(r8) :: phi(1:(size(a)+1)) - real(r8) :: detT ! determinant of inverse matrix - real(r8) :: b_product ! cumulative product of b coefficients - real(r8) :: c_product ! cumulative product of c coefficients - - character(len=*), parameter :: subname = 'tridiagonal_inverse' - !----------------------------------------------------------------------- - - !------------------------------------------------------------------------ - ! Here is the formula for coefficients of the inverse of a tridiagonal matrix: - ! - ! | a_1 b_1 | - ! | | - ! | c_1 a_2 b_2 | - ! | | - ! T = | c_2 a_3 ... | - ! | | - ! | ... ... b_n-1 | - ! | | - ! | c_n-1 a_n | - ! - ! Tinv(i,j) = (-1)^(i+j) * (b_i ... b_{j-1}) * theta_{i-1} * phi_{j+1} / theta_n if i <= j - ! - ! Tinv(i,j) = (-1)^(i+j) * (c_j ... b_{i-1}) * theta_{j-1} * phi_{i+1} / theta_n if i > j - ! - ! where theta_0 = 1 - ! theta_1 = a_1 - ! theta_i = a_i*theta_{i-1} - b_{i-1}*c_{i-1}*theta_{i-2} for i = 2, 3, ..., n - ! - ! phi_{n+1} = 1 - ! phi_n = a_n - ! phi_i = a_i*phi_{i+1} - b_i*c_i*phi_{i+2} for i = n-1, n-2, ..., 1 - ! - ! Note: For i = j, the b products are evaluated as b_i ... b_{j-1} = 1. - !------------------------------------------------------------------------ - - n = size(a) - SHR_ASSERT((size(b) == n), errMsg(__FILE__, __LINE__)) - SHR_ASSERT((size(c) == n), errMsg(__FILE__, __LINE__)) - SHR_ASSERT_ALL((shape(Tinv) == [n,n]), errMsg(__FILE__, __LINE__)) - - ! Compute theta recursively - ! Note: theta(n) is the determinant - - theta(0) = 1._r8 - theta(1) = a(1) - - do i = 2, n - theta(i) = a(i)*theta(i-1) - b(i-1)*c(i-1)*theta(i-2) - enddo - - detT = theta(n) - - ! Compute phi recursively - - phi(n+1) = 1._r8 - phi(n) = a(n) - - do i = n-1, 1, -1 - phi(i) = a(i)*phi(i+1) - b(i)*c(i)*phi(i+2) - enddo - - ! Compute coefficients of Tinv - - do j = 1, n - do i = 1, n - - if (i <= j) then - - ! compute product of b terms from i to j-1 - - b_product = 1 ! if i = j - if (i < j) then - do ii = i, j-1 - b_product = b_product*b(ii) - enddo - endif - - ! compute coefficient - - Tinv(i,j) = (-1)**(i+j) * b_product * theta(i-1) * phi(j+1) / detT - - else ! i > j - - ! compute product of c terms from j to i-1 - - c_product = 1 - do ii = j, i-1 - c_product = c_product*c(ii) - enddo - - ! compute coefficient - - Tinv(i,j) = (-1)**(i+j) * c_product * theta(j-1) * phi(i+1) / detT - - endif ! i >= j - - enddo ! i - enddo ! j - - - end subroutine tridiagonal_inverse - -end module shr_matrix_mod diff --git a/share/csm_share/test/unit/CMakeLists.txt b/share/csm_share/test/unit/CMakeLists.txt index eddd31964dd..f4f75b91af9 100644 --- a/share/csm_share/test/unit/CMakeLists.txt +++ b/share/csm_share/test/unit/CMakeLists.txt @@ -19,5 +19,3 @@ add_subdirectory(shr_vmath_test) add_subdirectory(shr_wv_sat_test) add_subdirectory(shr_precip_test) - -add_subdirectory(shr_matrix_test) diff --git a/share/csm_share/test/unit/shr_matrix_test/CMakeLists.txt b/share/csm_share/test/unit/shr_matrix_test/CMakeLists.txt deleted file mode 100644 index 561978073ff..00000000000 --- a/share/csm_share/test/unit/shr_matrix_test/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -# Local pFUnit files. -set(pf_sources - test_tridiagonal_inverse.pf) - -# Sources to test. -set(sources_needed - shr_assert_mod.F90 - shr_infnan_mod.F90 - shr_kind_mod.F90 - shr_log_mod.F90 - shr_matrix_mod.F90 - shr_strconvert_mod.F90 - shr_sys_mod.nompi_abortthrows.F90) -extract_sources("${sources_needed}" "${share_sources}" test_sources) - -# Do source preprocessing and add the executable. -create_pFUnit_test(shr_matrix_mod shr_matrix_mod_exe - "${pf_sources}" "${test_sources}") - -declare_generated_dependencies(shr_matrix_mod_exe "${share_genf90_sources}") \ No newline at end of file diff --git a/share/csm_share/test/unit/shr_matrix_test/test_tridiagonal_inverse.pf b/share/csm_share/test/unit/shr_matrix_test/test_tridiagonal_inverse.pf deleted file mode 100644 index 864df4f1898..00000000000 --- a/share/csm_share/test/unit/shr_matrix_test/test_tridiagonal_inverse.pf +++ /dev/null @@ -1,88 +0,0 @@ -module test_tridiagonal_inverse - - ! Tests of shr_matrix_mod: tridiagonal_inverse - - use pfunit_mod - use shr_matrix_mod - use shr_kind_mod , only : r8 => shr_kind_r8 - use shr_infnan_mod, only : nan => shr_infnan_nan, assignment(=) - - implicit none - - @TestCase - type, extends(TestCase) :: TestTridiagInverse - contains - procedure :: setUp - procedure :: tearDown - end type TestTridiagInverse - - real(r8), parameter :: tol = 1.e-13_r8 - -contains - - subroutine setUp(this) - class(TestTridiagInverse), intent(inout) :: this - end subroutine setUp - - subroutine tearDown(this) - class(TestTridiagInverse), intent(inout) :: this - end subroutine tearDown - - function tridiagonal_matrix(a, b, c) - ! Construct a tridiagonal matrix from vectors - real(r8), intent(in) :: a(:) ! Center diagonal - real(r8), intent(in) :: b(:) ! Upper diagonal (superdiagonal); b(n) is ignored - real(r8), intent(in) :: c(:) ! Lower diagonal (subdiagonal); c(n) is ignored - real(r8) :: tridiagonal_matrix(size(a), size(a)) - - integer :: i - integer :: n - - n = size(a) - @assertEqual(n, size(b)) - @assertEqual(n, size(c)) - - tridiagonal_matrix(:,:) = 0._r8 - do i = 1, n - tridiagonal_matrix(i, i) = a(i) - end do - do i = 1, n-1 - tridiagonal_matrix(i, i+1) = b(i) - end do - do i = 1, n-1 - tridiagonal_matrix(i+1, i) = c(i) - end do - end function tridiagonal_matrix - - function identity_matrix(n) - ! Returns the identity matrix of size n x n - real(r8) :: identity_matrix(n,n) - integer, intent(in) :: n - integer :: i - - identity_matrix(:,:) = 0._r8 - do i = 1, n - identity_matrix(i,i) = 1._r8 - end do - end function identity_matrix - - @Test - subroutine basic(this) - ! Do a basic test of tridiagonal_inverse - class(TestTridiagInverse), intent(inout) :: this - real(r8), parameter :: a(5) = [10._r8, 9._r8, 8._r8, 7._r8, 6._r8] - real(r8) :: b(5) = [11._r8, 12._r8, 14._r8, 13._r8, 0._r8] - real(r8) :: c(5) = [1._r8, 2._r8, 4._r8, 3._r8, 0._r8] - real(r8) :: arr(5,5) - real(r8) :: Tinv(5,5) - - b(5) = nan - c(5) = nan - - call tridiagonal_inverse(a, b, c, Tinv) - - arr = tridiagonal_matrix(a, b, c) - @assertEqual(identity_matrix(5), matmul(arr, Tinv), tolerance=tol) - end subroutine basic - -end module test_tridiagonal_inverse From 775ddea4c407e2e47e58e6599eb137ed8f6e6afb Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 6 May 2016 11:27:30 -0600 Subject: [PATCH 54/61] Add a comment Test suite: None Test baseline: N/A Test namelist changes: N/A Test status: N/A Fixes: None User interface changes?: No Code review: None --- .../driver/vertical_gradient_calculator_2nd_order.F90 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 index e38015eec77..b96d801566b 100644 --- a/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 +++ b/driver_cpl/driver/vertical_gradient_calculator_2nd_order.F90 @@ -122,6 +122,12 @@ function constructor(field, topo, elevclass_bounds) result(this) ! data. At a glance, it looks like the problems are just outside of Greenland, so this ! should be okay. When we have new TG forcing data, we should try uncommenting this ! call to check_topo. + ! + ! Alternatively, we could change check_topo to set a flag for each point saying + ! whether topo values are bad for that point. Then, when computing gradients, set + ! them to 0 for all points with bad topo values. (We did something similar for the + ! now-deleted vertical_gradient_calculator_continuous.) However, longer-term, an + ! abort may be more appropriate rather than silently setting gradients to 0. ! call this%check_topo() From 6cb731fbd9261aff6624143d82aac42d33bc9d96 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 6 May 2016 14:56:05 -0700 Subject: [PATCH 55/61] fix a bug or two --- cime_config/cesm/machines/Depends.intel | 3 +-- share/csm_share/shr/shr_frz_mod.F90.in | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cime_config/cesm/machines/Depends.intel b/cime_config/cesm/machines/Depends.intel index 334a1833102..5369c3f714f 100644 --- a/cime_config/cesm/machines/Depends.intel +++ b/cime_config/cesm/machines/Depends.intel @@ -4,8 +4,7 @@ prim_advection_mod.o \ edge_mod.o \ derivative_mod.o \ bndry_mod.o \ -prim_advance_mod.o \ -uwshcu.o +prim_advance_mod.o # CLM's SatellitePhenologyMod is compiled incorrectly with intel 15.0.0 at -O2 REDUCED_OPT_OBJS=\ diff --git a/share/csm_share/shr/shr_frz_mod.F90.in b/share/csm_share/shr/shr_frz_mod.F90.in index 1cca70790bd..3071559352d 100644 --- a/share/csm_share/shr/shr_frz_mod.F90.in +++ b/share/csm_share/shr/shr_frz_mod.F90.in @@ -101,7 +101,7 @@ / (-18.48_R8 + (0.01848_R8*max(s,0.0_R8))) else call shr_sys_abort(subname//' ERROR: not intialized correctly with a valid tfreeze_option - & - call shr_frz_freezetemp_init first with a valid tfreeze_option') + &call shr_frz_freezetemp_init first with a valid tfreeze_option') endif shr_frz_freezetemp = max(shr_frz_freezetemp,-2.0_R8) From aab40b3cc6cd029558f010a8664d1892039bc77f Mon Sep 17 00:00:00 2001 From: Chris Fischer Date: Mon, 9 May 2016 12:16:28 -0600 Subject: [PATCH 56/61] Remove reference to logan from config_compilers Test suite: Test baseline: Test namelist changes: Test status: bit for big Fixes: #119 User interface changes?: Code review: --- .../cesm/machines/config_compilers.xml | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/cime_config/cesm/machines/config_compilers.xml b/cime_config/cesm/machines/config_compilers.xml index c9684a4766d..a5a00200040 100644 --- a/cime_config/cesm/machines/config_compilers.xml +++ b/cime_config/cesm/machines/config_compilers.xml @@ -868,26 +868,6 @@ for mct, etc. - - mpi - /usr/lib64 - $(shell $(NETCDF_PATH)/bin/nf-config --flibs) - - - - -Wl,-rpath,$(NETCDF_PATH)/lib - -Wl,-rpath,$(COMPILER_PATH)/lib/intel64 - - - - -O2 - -O2 - -lgomp - -Wl,-R$(NETCDF_PATH)/lib - -Wl,-R$(COMPILER_PATH)/lib - -Wl,-R$(COMPILER_PATH)/libso - - /usr/local/netcdf-gcc-nag /usr/local/openmpi-gcc-nag From 7800cc7527e64e795ecd23d7f4c84dcaf7b233ec Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 9 May 2016 15:15:06 -0600 Subject: [PATCH 57/61] fix issue with esp_present on restart --- driver_cpl/driver/seq_rest_mod.F90 | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/driver_cpl/driver/seq_rest_mod.F90 b/driver_cpl/driver/seq_rest_mod.F90 index e8c5555047b..fa26e06d028 100644 --- a/driver_cpl/driver/seq_rest_mod.F90 +++ b/driver_cpl/driver/seq_rest_mod.F90 @@ -30,10 +30,10 @@ module seq_rest_mod use shr_file_mod, only: shr_file_getunit, shr_file_freeunit use mct_mod use ESMF - use component_type_mod + use component_type_mod ! diagnostic routines - use seq_diag_mct, only : budg_dataG, budg_ns + use seq_diag_mct, only : budg_dataG, budg_ns ! Sets mpi communicators, logunit and loglevel use seq_comm_mct, only: seq_comm_getdata=>seq_comm_setptrs, seq_comm_setnthreads, & @@ -43,18 +43,18 @@ module seq_rest_mod use seq_infodata_mod ! clock & alarm routines - use seq_timemgr_mod + use seq_timemgr_mod - ! diagnostic routines + ! diagnostic routines use seq_diag_mct, only: budg_datag ! lower level io routines - use seq_io_mod + use seq_io_mod ! prep modules - coupler communication between different components use prep_ocn_mod, only: prep_ocn_get_x2oacc_ox use prep_ocn_mod, only: prep_ocn_get_x2oacc_ox_cnt - use prep_rof_mod, only: prep_rof_get_l2racc_lx + use prep_rof_mod, only: prep_rof_get_l2racc_lx use prep_rof_mod, only: prep_rof_get_l2racc_lx_cnt use prep_glc_mod, only: prep_glc_get_l2gacc_lx use prep_glc_mod, only: prep_glc_get_l2gacc_lx_cnt @@ -183,7 +183,7 @@ subroutine seq_rest_read(rest_file, infodata, & ocn_present=ocn_present, & glc_present=glc_present, & wav_present=wav_present, & - esp_present=wav_present, & + esp_present=esp_present, & atm_prognostic=atm_prognostic, & lnd_prognostic=lnd_prognostic, & ice_prognostic=ice_prognostic, & @@ -235,22 +235,22 @@ subroutine seq_rest_read(rest_file, infodata, & if (ice_present) then gsmap => component_get_gsmap_cx(ice(1)) call seq_io_read(rest_file, gsmap, fractions_ix, 'fractions_ix') - call seq_io_read(rest_file, ice, 'c2x', 'i2x_ix') + call seq_io_read(rest_file, ice, 'c2x', 'i2x_ix') endif if (rof_present) then gsmap => component_get_gsmap_cx(rof(1)) call seq_io_read(rest_file, gsmap, fractions_rx, 'fractions_rx') - call seq_io_read(rest_file, rof, 'c2x', 'r2x_rx') + call seq_io_read(rest_file, rof, 'c2x', 'r2x_rx') endif if (glc_present) then gsmap => component_get_gsmap_cx(glc(1)) call seq_io_read(rest_file, gsmap, fractions_gx, 'fractions_gx') - call seq_io_read(rest_file, glc, 'c2x', 'g2x_gx') + call seq_io_read(rest_file, glc, 'c2x', 'g2x_gx') endif if (wav_present) then gsmap => component_get_gsmap_cx(wav(1)) call seq_io_read(rest_file, gsmap, fractions_wx, 'fractions_wx') - call seq_io_read(rest_file, wav, 'c2x', 'w2x_wx') + call seq_io_read(rest_file, wav, 'c2x', 'w2x_wx') endif ! Add ESP restart read here @@ -361,7 +361,7 @@ subroutine seq_rest_write(EClock_d, seq_SyncClock, infodata, & glc_prognostic=glc_prognostic, & wav_prognostic=wav_prognostic, & esp_prognostic=esp_prognostic, & - cpl_cdf64=cdf64, & + cpl_cdf64=cdf64, & case_name=case_name) ! Write out infodata and time manager data to restart file @@ -449,7 +449,7 @@ subroutine seq_rest_write(EClock_d, seq_SyncClock, infodata, & call seq_io_write(rest_file, gsmap, fractions_ax, 'fractions_ax', & whead=whead, wdata=wdata) call seq_io_write(rest_file, atm, 'c2x', 'a2x_ax', & - whead=whead, wdata=wdata) + whead=whead, wdata=wdata) call seq_io_write(rest_file, gsmap, xao_ax, 'xao_ax', & whead=whead, wdata=wdata) endif @@ -484,7 +484,7 @@ subroutine seq_rest_write(EClock_d, seq_SyncClock, infodata, & call seq_io_write(rest_file, gsmap, fractions_ox, 'fractions_ox', & whead=whead, wdata=wdata) call seq_io_write(rest_file, ocn, 'c2x', 'o2x_ox', & - whead=whead, wdata=wdata) + whead=whead, wdata=wdata) call seq_io_write(rest_file, gsmap, x2oacc_ox, 'x2oacc_ox', & whead=whead, wdata=wdata) call seq_io_write(rest_file, x2oacc_ox_cnt, 'x2oacc_ox_cnt', & @@ -497,7 +497,7 @@ subroutine seq_rest_write(EClock_d, seq_SyncClock, infodata, & call seq_io_write(rest_file, gsmap, fractions_ix, 'fractions_ix', & whead=whead, wdata=wdata) call seq_io_write(rest_file, ice, 'c2x', 'i2x_ix', & - whead=whead, wdata=wdata) + whead=whead, wdata=wdata) endif if (rof_present) then gsmap => component_get_gsmap_cx(rof(1)) @@ -511,14 +511,14 @@ subroutine seq_rest_write(EClock_d, seq_SyncClock, infodata, & call seq_io_write(rest_file, gsmap, fractions_gx, 'fractions_gx', & whead=whead, wdata=wdata) call seq_io_write(rest_file, glc, 'c2x', 'g2x_gx', & - whead=whead, wdata=wdata) + whead=whead, wdata=wdata) endif if (wav_present) then gsmap => component_get_gsmap_cx(wav(1)) call seq_io_write(rest_file, gsmap, fractions_wx, 'fractions_wx', & whead=whead, wdata=wdata) call seq_io_write(rest_file, wav, 'c2x', 'w2x_wx', & - whead=whead, wdata=wdata) + whead=whead, wdata=wdata) endif ! Write ESP restart data here enddo From 2db6710a4023d1f733f0b8face31a060a3e67685 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Tue, 10 May 2016 12:49:37 -0600 Subject: [PATCH 58/61] Add README file Test suite: None Test baseline: N/A Test namelist changes: N/A Test status: N/A Fixes: None User interface changes?: No Code review: None --- .../unit_test/vertical_gradient_calculator_test/README | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 driver_cpl/unit_test/vertical_gradient_calculator_test/README diff --git a/driver_cpl/unit_test/vertical_gradient_calculator_test/README b/driver_cpl/unit_test/vertical_gradient_calculator_test/README new file mode 100644 index 00000000000..7fa7d251bec --- /dev/null +++ b/driver_cpl/unit_test/vertical_gradient_calculator_test/README @@ -0,0 +1,10 @@ +The script plot_gradient and the example input file gradient_example.txt are not +directly related to the unit tests here. Rather, this script can be used to plot +the output from a gradient calculator, in a sort of "functional unit testing" +sense. + +If you look back at the history of this directory, you'll find some "functional +unit tests" that resulted in output files that could be plotted with this +script. However, these have been deleted because they were testing a vertical +gradient calculator implementation that no longer exists. + From 743836c1eb666904fc0b935ec18a45d8ca9e63aa Mon Sep 17 00:00:00 2001 From: Alice Bertini Date: Tue, 10 May 2016 16:39:45 -0600 Subject: [PATCH 59/61] update to st_archive to correctly check if interim restart file duplicates should be removed The st_archive was incorrectly removing duplicate rpointer files in the archive/$case/rest/[datename] directories to do an incorrect string comparison in the perl code. Also updated the archive.xml to match on rs1.* suffices for the data models. Test suite: A compset test with toggling of restart settings in order to debug Test baseline: N/A Test namelist changes: N/A Test status: bit for bit Fixes: #445 Code review: --- cime_config/cesm/archive.xml | 24 ++++++++++++++++++++++++ scripts/Tools/st_archive | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/cime_config/cesm/archive.xml b/cime_config/cesm/archive.xml index 1177673181f..e0eb788b06e 100644 --- a/cime_config/cesm/archive.xml +++ b/cime_config/cesm/archive.xml @@ -100,6 +100,10 @@ rest true + + rest + true + hist true @@ -188,6 +192,10 @@ rest true + + rest + true + hist true @@ -298,6 +306,10 @@ rest true + + rest + true + hist true @@ -338,6 +350,10 @@ rest true + + rest + true + hist true @@ -426,6 +442,10 @@ rest true + + rest + true + hist false @@ -492,6 +512,10 @@ rest true + + rest + true + hist false diff --git a/scripts/Tools/st_archive b/scripts/Tools/st_archive index c8dcaa5edbf..e1888114669 100755 --- a/scripts/Tools/st_archive +++ b/scripts/Tools/st_archive @@ -720,7 +720,7 @@ sub main # run the archive process archiveProcess( $XMLin, $dname, \@runfiles ); - if( uc($config{'DOUT_S_SAVE_INTERIM_RESTART_FILES'}) == 'TRUE' ) + if( uc($config{'DOUT_S_SAVE_INTERIM_RESTART_FILES'}) eq 'TRUE' ) { # remove restart duplicate files from the comp/rest and rest/dname directories removeDups( $dname ); From 380eb15de4b46eb56ce37d2ab3af32b9f0a61728 Mon Sep 17 00:00:00 2001 From: Alice Bertini Date: Thu, 12 May 2016 12:16:32 -0600 Subject: [PATCH 60/61] Minor fix to st_archive to exclude removing rpointer files When the DOUT_S_SAVE_INTERIM_RESTART_FILES=TRUE the st_archive incorrectly deleted rpointer files from previous restart directories. This fix excludes rpointer files from the removeDups function. Test suite: A compset continue run with modified XMLsettings Test baseline: N/A Test namelist changes: N/A Test status: bit for bit Fixes: #445 Code review: --- scripts/Tools/st_archive | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Tools/st_archive b/scripts/Tools/st_archive index e1888114669..6987982e358 100755 --- a/scripts/Tools/st_archive +++ b/scripts/Tools/st_archive @@ -661,7 +661,7 @@ sub removeDups my @matchfiles = File::Find::Rule->file->name($file)->in($config{'DOUT_S_ROOT'}); foreach my $matchfile (@matchfiles) { - if( $matchfile =~ /\/rest\// && $matchfile ne $restfile && $matchfile !~ /\/hist\// ) { + if( $matchfile =~ /\/rest\// && $matchfile ne $restfile && $matchfile !~ /\/hist\// && $matchfile !~ /rpointer/) { $logger->debug("Dedebugng matchfile=$matchfile matches restfile=$restfile"); unlink( $matchfile ); } From 42696c938dea64b83568bac67e2deee07d60b5a7 Mon Sep 17 00:00:00 2001 From: Chris Fischer Date: Tue, 31 May 2016 16:00:32 -0600 Subject: [PATCH 61/61] Removed inputdata files that referenced glade from usermods. Test suite: Test baseline: Test namelist changes: Test status: Answer changes for B1850 that uses usermods Fixes: User interface changes?: Code review: --- .../allactive/usermods_dirs/b1850/user_nl_clm | 4 ---- .../allactive/usermods_dirs/b1850/user_nl_pop | 16 ---------------- 2 files changed, 20 deletions(-) delete mode 100644 cime_config/cesm/allactive/usermods_dirs/b1850/user_nl_pop diff --git a/cime_config/cesm/allactive/usermods_dirs/b1850/user_nl_clm b/cime_config/cesm/allactive/usermods_dirs/b1850/user_nl_clm index 0d7da72dd72..4c91174e7dc 100644 --- a/cime_config/cesm/allactive/usermods_dirs/b1850/user_nl_clm +++ b/cime_config/cesm/allactive/usermods_dirs/b1850/user_nl_clm @@ -1,5 +1 @@ -fsurdat = '/glade/p/cesmdata/cseg/inputdata/lnd/clm2/surfdata_map/surfdata_0.9x1.25_16pftsmodwshrubwglcr_simyr1850_c151210.nc' init_interp_fill_missing_with_natveg = .true. -finidat = '/glade/scratch/hannay/land_ic/b.e15.B1850.f09_g16.pi_control.35_ic.clm2.r.0001-01-06-00000.nc' - - diff --git a/cime_config/cesm/allactive/usermods_dirs/b1850/user_nl_pop b/cime_config/cesm/allactive/usermods_dirs/b1850/user_nl_pop deleted file mode 100644 index 3c7aca28612..00000000000 --- a/cime_config/cesm/allactive/usermods_dirs/b1850/user_nl_pop +++ /dev/null @@ -1,16 +0,0 @@ -alk_riv_flux_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/river_nutrients_GNEWS2000_gx1v6_no_ms_c150702.nc' -dfe_riv_flux_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/river_nutrients_GNEWS2000_gx1v6_no_ms_c150702.nc' -dic_riv_flux_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/river_nutrients_GNEWS2000_gx1v6_no_ms_c150702.nc' -din_riv_flux_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/river_nutrients_GNEWS2000_gx1v6_no_ms_c150702.nc' -dip_riv_flux_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/river_nutrients_GNEWS2000_gx1v6_no_ms_c150702.nc' -doc_riv_flux_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/river_nutrients_GNEWS2000_gx1v6_no_ms_c150702.nc' -don_riv_flux_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/river_nutrients_GNEWS2000_gx1v6_no_ms_c150702.nc' -dop_riv_flux_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/river_nutrients_GNEWS2000_gx1v6_no_ms_c150702.nc' -dsi_riv_flux_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/river_nutrients_GNEWS2000_gx1v6_no_ms_c150702.nc' -dust_flux_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/dst79gnx_gx1v6_090416_no_ms_c150702.nc' -fesedflux_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/fesedflux_gx1v6_etopo2v2_Nov2015_vents_no_ms_c151218.nc' -iron_flux_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/solFe_scenario4_current_gx1v6_no_ms_c150702.nc' -nhy_flux_monthly_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/ndep_ocn_1850_gx1v6_no_ms_c150702.nc' -nox_flux_monthly_input%filename = '/glade/p/cesm/bgcwg/forcing/BEC_gx1v6_forcing_no_ms/ndep_ocn_1850_gx1v6_no_ms_c150702.nc' - -