Repository navigation
Use reproducible global sums when BFBFLAG is set #704
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
67d6510
422d4ef
e58dd4c
c1960de
5fe5dc7
2fc288b
bdfc64e
d828c22
74dbb60
b022c95
e151743
0b39c0a
b674904
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| module med_global_sums_mod | ||
|
|
||
| !----------------------------------------------------------------------------- | ||
| ! Computation of global sums, optionally in a manner that is independent of processor count. | ||
| ! | ||
| ! NOTE: This is deliberately duplicated from dshr_global_sums in | ||
| ! cdeps/dshr/dshr_global_sums_mod.F90. The duplication is accepted because neither CMEPS nor | ||
| ! CDEPS is available in all of the builds that need this functionality, and putting it in the | ||
| ! CESM share code would require a separate, nearly complete reimplementation for builds that | ||
| ! do not have access to that code. If you change this module, make the corresponding change | ||
| ! in CDEPS. | ||
| !----------------------------------------------------------------------------- | ||
|
|
||
| use med_kind_mod, only : r8=>SHR_KIND_R8, cs=>SHR_KIND_CS | ||
| use ESMF , only : ESMF_GridComp, ESMF_GridCompGet, ESMF_VM, ESMF_VMGet | ||
| use ESMF , only : ESMF_VMAllreduce, ESMF_REDUCE_SUM, ESMF_SUCCESS | ||
| use NUOPC , only : NUOPC_CompAttributeGet | ||
| use shr_log_mod, only : shr_log_error | ||
| use med_utils_mod, only : chkerr => med_utils_ChkErr | ||
| #ifdef CESMCOUPLED | ||
| use shr_reprosum_mod, only : shr_reprosum_calc | ||
| #endif | ||
|
|
||
| implicit none | ||
| private | ||
|
|
||
| public :: med_global_sums | ||
|
|
||
| character(*), parameter :: u_FILE_u = & | ||
| __FILE__ | ||
|
|
||
| ! med_global_sums is generic over the number of fields being summed: the 1fld version takes | ||
| ! summands for a single field and returns a scalar sum; the nflds version takes summands for | ||
| ! an arbitrary number of fields and returns an array of sums. | ||
| interface med_global_sums | ||
| module procedure med_global_sums_1fld | ||
| module procedure med_global_sums_nflds | ||
| end interface med_global_sums | ||
|
|
||
| !=============================================================================== | ||
| contains | ||
| !=============================================================================== | ||
|
|
||
| subroutine med_global_sums_nflds(gcomp, local_summands, global_sums, rc) | ||
|
|
||
| ! Compute global sums of the given local summands. | ||
| ! | ||
| ! local_summands has dimensions [number of local grid cells, number of fields]; | ||
| ! global_sums has dimensions [number of fields]. | ||
| ! | ||
| ! If the bfbflag attribute is true, the sums are computed in a manner that is | ||
| ! independent of processor count; otherwise a cheaper, processor-count-dependent | ||
| ! algorithm is used. Note that, for the sums to truly be independent of processor | ||
| ! count, the caller needs to provide the individual summands here rather than | ||
| ! pre-summing them locally. | ||
|
|
||
| ! input/output variables | ||
| type(ESMF_GridComp), intent(in) :: gcomp | ||
| real(r8), intent(in) :: local_summands(:,:) | ||
| real(r8), intent(out) :: global_sums(:) | ||
| integer , intent(out) :: rc | ||
|
|
||
| ! local variables | ||
| type(ESMF_VM) :: vm | ||
| integer :: nsummands ! number of local grid cells | ||
| integer :: nflds ! number of fields | ||
| integer :: n, nf | ||
| #ifdef CESMCOUPLED | ||
| integer :: mpicom ! MPI communicator of this component | ||
| #endif | ||
| character(len=CS) :: cvalue | ||
| logical :: isPresent, isSet | ||
| logical :: bfbflag ! value of the bfbflag attribute | ||
| real(r8) :: local_sums(size(local_summands, 2)) ! local sums over grid cells, for each field | ||
|
|
||
| character(len=*), parameter :: subname = '(med_global_sums_nflds)' | ||
| !--------------------------------------------------------------- | ||
|
|
||
| rc = ESMF_SUCCESS | ||
|
|
||
| call NUOPC_CompAttributeGet(gcomp, name='bfbflag', value=cvalue, & | ||
| isPresent=isPresent, isSet=isSet, rc=rc) | ||
| if (chkerr(rc,__LINE__,u_FILE_u)) return | ||
| bfbflag = .false. | ||
| if (isPresent .and. isSet) then | ||
| read(cvalue,*) bfbflag | ||
| end if | ||
|
|
||
| nsummands = size(local_summands, 1) | ||
| nflds = size(local_summands, 2) | ||
|
|
||
| if (size(global_sums) /= nflds) then | ||
| call shr_log_error(subname//' ERROR: size of global_sums must agree with the '// & | ||
| 'second dimension of local_summands', line=__LINE__, file=u_FILE_u, rc=rc) | ||
| return | ||
| end if | ||
|
|
||
| call ESMF_GridCompGet(gcomp, vm=vm, rc=rc) | ||
| if (chkerr(rc,__LINE__,u_FILE_u)) return | ||
|
|
||
| if (bfbflag) then | ||
| #ifdef CESMCOUPLED | ||
| call ESMF_VMGet(vm, mpiCommunicator=mpicom, rc=rc) | ||
| if (chkerr(rc,__LINE__,u_FILE_u)) return | ||
| call shr_reprosum_calc(arr=local_summands, arr_gsum=global_sums, & | ||
| nsummands=nsummands, dsummands=nsummands, nflds=nflds, commid=mpicom) | ||
| #else | ||
| call shr_log_error(subname//' ERROR: bfbflag is true, but this build does not have '// & | ||
| 'access to the shr_reprosum module needed for bfbflag to be true', & | ||
| line=__LINE__, file=u_FILE_u, rc=rc) | ||
| return | ||
| #endif | ||
| else | ||
| do nf = 1, nflds | ||
| local_sums(nf) = 0.0_r8 | ||
| do n = 1, nsummands | ||
| local_sums(nf) = local_sums(nf) + local_summands(n,nf) | ||
| end do | ||
| end do | ||
| call ESMF_VMAllreduce(vm, senddata=local_sums, recvdata=global_sums, count=nflds, & | ||
| reduceflag=ESMF_REDUCE_SUM, rc=rc) | ||
| if (chkerr(rc,__LINE__,u_FILE_u)) return | ||
| end if | ||
|
|
||
| end subroutine med_global_sums_nflds | ||
|
|
||
| !=============================================================================== | ||
|
|
||
| subroutine med_global_sums_1fld(gcomp, local_summands, global_sum, rc) | ||
|
|
||
| ! Compute the global sum of the given local summands, for a single field. | ||
| ! | ||
| ! This is a convenience wrapper around med_global_sums_nflds for the common case of a | ||
| ! single field; see that routine for details. | ||
|
|
||
| ! input/output variables | ||
| type(ESMF_GridComp), intent(in) :: gcomp | ||
| real(r8) , intent(in) :: local_summands(:) | ||
| real(r8) , intent(out) :: global_sum | ||
| integer , intent(out) :: rc | ||
|
|
||
| ! local variables | ||
| real(r8), allocatable :: summands_2d(:,:) | ||
| real(r8) :: global_sums(1) | ||
| !--------------------------------------------------------------- | ||
|
|
||
| rc = ESMF_SUCCESS | ||
|
|
||
| allocate(summands_2d(size(local_summands), 1)) | ||
| summands_2d(:,1) = local_summands(:) | ||
| call med_global_sums_nflds(gcomp, summands_2d, global_sums, rc) | ||
| if (chkerr(rc,__LINE__,u_FILE_u)) return | ||
| global_sum = global_sums(1) | ||
|
|
||
| end subroutine med_global_sums_1fld | ||
|
|
||
| end module med_global_sums_mod | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,16 +6,16 @@ module med_phases_post_rof_mod | |
| use NUOPC , only : NUOPC_CompAttributeGet | ||
| use ESMF , only : ESMF_Clock, ESMF_ClockIsCreated | ||
| use ESMF , only : ESMF_LogWrite, ESMF_LOGMSG_INFO, ESMF_SUCCESS | ||
| use ESMF , only : ESMF_GridComp, ESMF_GridCompGet | ||
| use ESMF , only : ESMF_GridComp | ||
| use ESMF , only : ESMF_Mesh, ESMF_MESHLOC_ELEMENT, ESMF_TYPEKIND_R8 | ||
| use ESMF , only : ESMF_Field | ||
| use ESMF , only : ESMF_FieldBundle, ESMF_FieldBundleCreate | ||
| use ESMF , only : ESMF_FieldBundleGet, ESMF_FieldBundleAdd | ||
| use ESMF , only : ESMF_VM, ESMF_VMAllreduce, ESMF_REDUCE_SUM | ||
| use med_kind_mod , only : CX=>SHR_KIND_CX, CS=>SHR_KIND_CS, CL=>SHR_KIND_CL, R8=>SHR_KIND_R8 | ||
| use med_internalstate_mod , only : complnd, compocn, compice, comprof | ||
| use med_internalstate_mod , only : InternalState, maintask, logunit | ||
| use med_utils_mod , only : chkerr => med_utils_ChkErr | ||
| use med_global_sums_mod , only : med_global_sums | ||
| use med_constants_mod , only : dbug_flag => med_constants_dbug_flag | ||
| use med_phases_history_mod, only : med_phases_history_write_comp | ||
| use med_map_mod , only : med_map_field_packed | ||
|
|
@@ -323,15 +323,18 @@ subroutine med_phases_post_rof_remove_negative_runoff(gcomp, field_name, rc) | |
|
|
||
| ! local variables | ||
| type(InternalState) :: is_local | ||
| type(ESMF_VM) :: vm | ||
| real(r8), pointer :: runoff_flux(:) ! temporary 1d pointer | ||
| real(r8), pointer :: areas(:) | ||
| real(r8) :: local_positive(1), global_positive(1) | ||
| real(r8) :: local_negative(1), global_negative(1) | ||
| ! The local_* variables below hold per-grid-cell contributions to the corresponding global | ||
| ! sums. These are kept per grid cell, rather than being summed up locally here, so that the | ||
| ! global sums can be computed in a manner that is independent of processor count if bfbflag | ||
| ! is set. | ||
| real(r8), allocatable :: local_positive(:), local_negative(:) | ||
| real(r8) :: global_positive, global_negative | ||
| real(r8) :: global_sum | ||
| real(r8) :: multiplier | ||
| real(r8) :: local_positive_final(1), global_positive_final(1) | ||
| real(r8) :: local_negative_final(1), global_negative_final(1) | ||
| real(r8), allocatable :: local_positive_final(:), local_negative_final(:) | ||
| real(r8) :: global_positive_final, global_negative_final | ||
| real(r8) :: global_sum_final | ||
| integer :: n | ||
|
|
||
|
|
@@ -358,36 +361,36 @@ subroutine med_phases_post_rof_remove_negative_runoff(gcomp, field_name, rc) | |
| call fldbun_getdata1d(FBrof_r, trim(field_name), runoff_flux, rc=rc) | ||
| if (ChkErr(rc,__LINE__,u_FILE_u)) return | ||
|
|
||
| local_positive(1) = 0.0_r8 | ||
| local_negative(1) = 0.0_r8 | ||
| allocate(local_positive(size(runoff_flux))) | ||
| allocate(local_negative(size(runoff_flux))) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a check for error status on the allocate? |
||
| local_positive(:) = 0.0_r8 | ||
| local_negative(:) = 0.0_r8 | ||
| do n = 1, size(runoff_flux) | ||
| if (runoff_flux(n) >= 0.0_r8) then | ||
| local_positive(1) = local_positive(1) + areas(n) * runoff_flux(n) | ||
| local_positive(n) = areas(n) * runoff_flux(n) | ||
| else | ||
| local_negative(1) = local_negative(1) + areas(n) * runoff_flux(n) | ||
| local_negative(n) = areas(n) * runoff_flux(n) | ||
| end if | ||
| end do | ||
|
|
||
| call ESMF_GridCompGet(gcomp, vm=vm, rc=rc) | ||
| call med_global_sums(gcomp, local_positive, global_positive, rc) | ||
| if (ChkErr(rc,__LINE__,u_FILE_u)) return | ||
| call ESMF_VMAllreduce(vm, senddata=local_positive, recvdata=global_positive, count=1, & | ||
| reduceflag=ESMF_REDUCE_SUM, rc=rc) | ||
| call med_global_sums(gcomp, local_negative, global_negative, rc) | ||
| if (ChkErr(rc,__LINE__,u_FILE_u)) return | ||
| call ESMF_VMAllreduce(vm, senddata=local_negative, recvdata=global_negative, count=1, & | ||
| reduceflag=ESMF_REDUCE_SUM, rc=rc) | ||
| if (ChkErr(rc,__LINE__,u_FILE_u)) return | ||
| global_sum = global_positive(1) + global_negative(1) | ||
| deallocate(local_positive) | ||
| deallocate(local_negative) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe a check to make sure it's allocated before deallocating?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is another conversation that I think should be taken outside this PR review if you think it's important: From my quick look, it looks like we rarely check the allocated status of arrays before deallocating in CMEPS, and it seems unnecessary here because they are allocated unconditionally. (I have a feeling that the places that do check the allocation status are handling situations where the array may truly be unallocated at that point - for example, I see this in a "clean" routine that I think I may have written, which is handling the possibility that "clean" is called - e.g., from a unit test - despite the array not having been allocated.) So this would be a broader discussion on the preferred pattern in general. Actually, I think it would be safe to completely remove these deallocate statements, because since Fortran 2003 allocatables are automatically deallocated when they go out of scope. I thought about doing that, and would personally prefer removing them completely over adding a conditional check around the deallocate.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, TIL. Well, most of my Fortran has this check in it, both CISM and CAM. Mostly in case the code changes in a way that could result in the array being unallocated when the deallocate is called. I agree it's not super necessary here.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fair enough. I see that rationale. Personally, I think I'd look at that and say, "why is this only conditionally deallocated? what is the situation where it wouldn't have been allocated?" But I certainly see your perspective on this, too, and it's a good one. |
||
| global_sum = global_positive + global_negative | ||
| if (maintask .and. dbug_flag > dbug_threshold) then | ||
| write(logunit,'(a)') subname//' Before correction: '//trim(field_name) | ||
| write(logunit,'(a,e27.17)') subname//' global_positive = ', global_positive(1) | ||
| write(logunit,'(a,e27.17)') subname//' global_negative = ', global_negative(1) | ||
| write(logunit,'(a,e27.17)') subname//' global_positive = ', global_positive | ||
| write(logunit,'(a,e27.17)') subname//' global_negative = ', global_negative | ||
| write(logunit,'(a,e27.17)') subname//' global_sum = ', global_sum | ||
| end if | ||
|
|
||
| if (global_sum > 0.0_r8) then | ||
| ! There is enough positive runoff to absorb all of the negative runoff; so set | ||
| ! negative runoff to 0 and downweight positive runoff to conserve. | ||
| multiplier = global_sum/global_positive(1) | ||
| multiplier = global_sum/global_positive | ||
| do n = 1, size(runoff_flux) | ||
| if (runoff_flux(n) > 0.0_r8) then | ||
| runoff_flux(n) = runoff_flux(n) * multiplier | ||
|
|
@@ -399,7 +402,7 @@ subroutine med_phases_post_rof_remove_negative_runoff(gcomp, field_name, rc) | |
| ! There is more negative than positive runoff. Hopefully this happens rarely, if | ||
| ! ever; so set positive runoff to 0 and downweight negative runoff to minimize | ||
| ! negative runoff and conserve. | ||
| multiplier = global_sum/global_negative(1) | ||
| multiplier = global_sum/global_negative | ||
| do n = 1, size(runoff_flux) | ||
| if (runoff_flux(n) < 0.0_r8) then | ||
| runoff_flux(n) = runoff_flux(n) * multiplier | ||
|
|
@@ -417,26 +420,28 @@ subroutine med_phases_post_rof_remove_negative_runoff(gcomp, field_name, rc) | |
|
|
||
| if (dbug_flag > dbug_threshold) then | ||
| ! Recompute positives, negatives and total sum for output diagnostic purposes | ||
| local_positive_final(1) = 0.0_r8 | ||
| local_negative_final(1) = 0.0_r8 | ||
| allocate(local_positive_final(size(runoff_flux))) | ||
| allocate(local_negative_final(size(runoff_flux))) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Safety checks on these allocates? |
||
| local_positive_final(:) = 0.0_r8 | ||
| local_negative_final(:) = 0.0_r8 | ||
| do n = 1, size(runoff_flux) | ||
| if (runoff_flux(n) >= 0.0_r8) then | ||
| local_positive_final(1) = local_positive_final(1) + areas(n) * runoff_flux(n) | ||
| local_positive_final(n) = areas(n) * runoff_flux(n) | ||
| else | ||
| local_negative_final(1) = local_negative_final(1) + areas(n) * runoff_flux(n) | ||
| local_negative_final(n) = areas(n) * runoff_flux(n) | ||
| end if | ||
| end do | ||
| call ESMF_VMAllreduce(vm, senddata=local_positive_final, recvdata=global_positive_final, count=1, & | ||
| reduceflag=ESMF_REDUCE_SUM, rc=rc) | ||
| call med_global_sums(gcomp, local_positive_final, global_positive_final, rc) | ||
| if (ChkErr(rc,__LINE__,u_FILE_u)) return | ||
| call ESMF_VMAllreduce(vm, senddata=local_negative_final, recvdata=global_negative_final, count=1, & | ||
| reduceflag=ESMF_REDUCE_SUM, rc=rc) | ||
| call med_global_sums(gcomp, local_negative_final, global_negative_final, rc) | ||
| if (ChkErr(rc,__LINE__,u_FILE_u)) return | ||
| global_sum_final = global_positive_final(1) + global_negative_final(1) | ||
| deallocate(local_positive_final) | ||
| deallocate(local_negative_final) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And checks here as well |
||
| global_sum_final = global_positive_final + global_negative_final | ||
| if (maintask) then | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, outside the scope of these changes, but the writes above included "dbug_flag > dbug_threshold" in this check and I think that might be good here to reduce log output.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole block of code is already inside a conditional on |
||
| write(logunit,'(a)') subname//' After correction: '//trim(field_name) | ||
| write(logunit,'(a,e27.17)') subname//' global_positive_final = ', global_positive_final(1) | ||
| write(logunit,'(a,e27.17)') subname//' global_negative_final = ', global_negative_final(1) | ||
| write(logunit,'(a,e27.17)') subname//' global_positive_final = ', global_positive_final | ||
| write(logunit,'(a,e27.17)') subname//' global_negative_final = ', global_negative_final | ||
| write(logunit,'(a,e27.17)') subname//' global_sum_final = ', global_sum_final | ||
| end if | ||
| end if | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I was feeling like a check was needed here, and I figured out what I was thinking is adding a return stat in the allocate call and checking to make sure it was successful. Like:
`allocate(summands_2d(size(local_summands), 1), stat=ierr)
if (ierr /= 0) then
errstring='Error allocating summands_2d'`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize that feelings differ across CSEG on this point (with CAM apparently preferring a stat check). I personally prefer to not have an explicit check of the stat of an allocate: if you don't query the stat, then the code will abort if there's an error in allocation; if you do query the stat, then it's up to you to handle the error. This leaves open the possibility that you'll make an error in the error handling itself, accidentally letting an allocation error slip through without aborting. I think I've seen this kind of bug in the error handling of an allocate in a recent code review. I realize that catching the error can allow for a more informative error message, but my feeling is that it's not worth the extra code and the possibility of inserting a bug in the error-handling code.
I'm open to having my mind changed on this point, but I think this is a bigger discussion that should be taken outside of this PR review: the vast majority of allocates in CMEPS do not check the stat, so I prefer to stay consistent with the pre-existing pattern until we have a discussion where the consensus is that we want to add this pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Bill, no worries. I think the fact that this is in-line with CMEPS standards is good enough. I've had too many reviews where CAM SEs (no need to name names!) would flag all of mine, so I just want to make sure we at least consider the choice.