Skip to content

Commit 8834f68

Browse files
c2xukshedstrom
authored andcommitted
Inline harmonic analysis (NOAA-GFDL#744)
* Inline harmonic analysis Important bug fix: 1) The Cholesky decomposition was operating on entries below the main diagonal of FtF, whereas in the accumulator of FtF, only entries along and above the main diagonal were calculated. In this revision, I modified HA_accum_FtF so that entries below the main diagonal are accumulated instead. 2) In the accumulator of FtSSH, the first entry for the mean (zero frequency) is moved out of the loop over different tidal constituents, so that it is not accumulated multiple times within a single time step. * Inline harmonic analysis Another bug fix: initial state added back to the mean state. * Inline harmonic analysis Minor update to HA_solver
1 parent 822257e commit 8834f68

File tree

1 file changed

+55
-48
lines changed

1 file changed

+55
-48
lines changed

src/diagnostics/MOM_harmonic_analysis.F90

Lines changed: 55 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ end subroutine HA_register
173173

174174
!> This subroutine accumulates the temporal basis functions in FtF.
175175
!! The tidal constituents are those used in MOM_tidal_forcing, plus the mean (of zero frequency).
176+
!! Only the main diagonal and entries below it are calculated, which are needed for Cholesky decomposition.
176177
subroutine HA_accum_FtF(Time, CS)
177178
type(time_type), intent(in) :: Time !< The current model time
178179
type(harmonic_analysis_CS), intent(inout) :: CS !< Control structure of the MOM_harmonic_analysis module
@@ -191,27 +192,31 @@ subroutine HA_accum_FtF(Time, CS)
191192
nc = CS%nc
192193
now = CS%US%s_to_T * time_type_to_real(Time - CS%time_ref)
193194

194-
! Accumulate FtF
195-
CS%FtF(1,1) = CS%FtF(1,1) + 1.0 !< For the zero frequency
195+
!< First entry, corresponding to the zero frequency constituent (mean)
196+
CS%FtF(1,1) = CS%FtF(1,1) + 1.0
197+
196198
do c=1,nc
197199
icos = 2*c
198200
isin = 2*c+1
199201
cosomegat = cos(CS%freq(c) * now + CS%phase0(c))
200202
sinomegat = sin(CS%freq(c) * now + CS%phase0(c))
203+
204+
! First column, corresponding to the zero frequency constituent (mean)
201205
CS%FtF(icos,1) = CS%FtF(icos,1) + cosomegat
202206
CS%FtF(isin,1) = CS%FtF(isin,1) + sinomegat
203-
CS%FtF(1,icos) = CS%FtF(icos,1)
204-
CS%FtF(1,isin) = CS%FtF(isin,1)
205-
do cc=c,nc
207+
208+
do cc=1,c
206209
iccos = 2*cc
207210
issin = 2*cc+1
208211
ccosomegat = cos(CS%freq(cc) * now + CS%phase0(cc))
209212
ssinomegat = sin(CS%freq(cc) * now + CS%phase0(cc))
213+
214+
! Interior of the matrix, corresponding to the products of cosine and sine terms
210215
CS%FtF(icos,iccos) = CS%FtF(icos,iccos) + cosomegat * ccosomegat
211216
CS%FtF(icos,issin) = CS%FtF(icos,issin) + cosomegat * ssinomegat
212217
CS%FtF(isin,iccos) = CS%FtF(isin,iccos) + sinomegat * ccosomegat
213218
CS%FtF(isin,issin) = CS%FtF(isin,issin) + sinomegat * ssinomegat
214-
enddo ! cc=c,nc
219+
enddo ! cc=1,c
215220
enddo ! c=1,nc
216221

217222
end subroutine HA_accum_FtF
@@ -276,14 +281,18 @@ subroutine HA_accum_FtSSH(key, data, Time, G, CS)
276281

277282
is = ha1%is ; ie = ha1%ie ; js = ha1%js ; je = ha1%je
278283

279-
! Accumulate FtF and FtSSH
284+
!< First entry, corresponding to the zero frequency constituent (mean)
285+
do j=js,je ; do i=is,ie
286+
ha1%FtSSH(i,j,1) = ha1%FtSSH(i,j,1) + (data(i,j) - ha1%ref(i,j))
287+
enddo ; enddo
288+
289+
!< The remaining entries
280290
do c=1,nc
281291
icos = 2*c
282292
isin = 2*c+1
283293
cosomegat = cos(CS%freq(c) * now + CS%phase0(c))
284294
sinomegat = sin(CS%freq(c) * now + CS%phase0(c))
285295
do j=js,je ; do i=is,ie
286-
ha1%FtSSH(i,j,1) = ha1%FtSSH(i,j,1) + (data(i,j) - ha1%ref(i,j))
287296
ha1%FtSSH(i,j,icos) = ha1%FtSSH(i,j,icos) + (data(i,j) - ha1%ref(i,j)) * cosomegat
288297
ha1%FtSSH(i,j,isin) = ha1%FtSSH(i,j,isin) + (data(i,j) - ha1%ref(i,j)) * sinomegat
289298
enddo ; enddo
@@ -315,7 +324,7 @@ subroutine HA_write(ha1, Time, G, CS)
315324
! Local variables
316325
real, dimension(:,:,:), allocatable :: FtSSHw !< An array containing the harmonic constants [A]
317326
integer :: year, month, day, hour, minute, second
318-
integer :: nc, k, is, ie, js, je
327+
integer :: nc, i, j, k, is, ie, js, je
319328

320329
character(len=255) :: filename !< Output file name
321330
type(MOM_infra_file) :: cdf !< The file handle for output harmonic constants
@@ -348,6 +357,11 @@ subroutine HA_write(ha1, Time, G, CS)
348357
call create_MOM_file(cdf, trim(filename), cdf_vars, &
349358
2*nc+1, cdf_fields, SINGLE_FILE, 86400.0, G=G)
350359

360+
! Add the initial field back to the mean state
361+
do j=js,je ; do i=is,ie
362+
FtSSHw(i,j,1) = FtSSHw(i,j,1) + ha1%ref(i,j)
363+
enddo ; enddo
364+
351365
! Write data
352366
call MOM_write_field(cdf, cdf_fields(1), G%domain, FtSSHw(:,:,1), 0.0)
353367
do k=1,nc
@@ -362,75 +376,68 @@ subroutine HA_write(ha1, Time, G, CS)
362376

363377
end subroutine HA_write
364378

365-
!> This subroutine computes the harmonic constants (stored in FtSSHw) using the dot products of the temporal
379+
!> This subroutine computes the harmonic constants (stored in x) using the dot products of the temporal
366380
!! basis functions accumulated in FtF, and the dot products of the SSH (or other fields) with the temporal basis
367381
!! functions accumulated in FtSSH. The system is solved by Cholesky decomposition,
368382
!!
369-
!! FtF * FtSSHw = FtSSH, => FtFw * (FtFw' * FtSSHw) = FtSSH,
383+
!! FtF * x = FtSSH, => L * (L' * x) = FtSSH, => L * y = FtSSH,
370384
!!
371-
!! where FtFw is a lower triangular matrix, and the prime denotes matrix transpose.
385+
!! where L is the lower triangular matrix, y = L' * x, and x is the solution vector.
372386
!!
373-
subroutine HA_solver(ha1, nc, FtF, FtSSHw)
387+
subroutine HA_solver(ha1, nc, FtF, x)
374388
type(HA_type), pointer, intent(in) :: ha1 !< Control structure for the current field
375389
integer, intent(in) :: nc !< Number of harmonic constituents
376390
real, dimension(:,:), intent(in) :: FtF !< Accumulator of (F' * F) for all fields [nondim]
377-
real, dimension(:,:,:), allocatable, intent(out) :: FtSSHw !< Work array for Cholesky decomposition [A]
391+
real, dimension(ha1%is:ha1%ie,ha1%js:ha1%je,2*nc+1), &
392+
intent(out) :: x !< Solution vector of harmonic constants [A]
378393

379394
! Local variables
380-
real :: tmp0 !< Temporary variable for Cholesky decomposition [nondim]
381-
real, dimension(:), allocatable :: tmp1 !< Temporary variable for Cholesky decomposition [nondim]
382-
real, dimension(:,:), allocatable :: tmp2 !< Temporary variable for Cholesky decomposition [A]
383-
real, dimension(:,:), allocatable :: FtFw !< Lower triangular matrix for Cholesky decomposition [nondim]
384-
integer :: k, m, n, is, ie, js, je
385-
386-
is = ha1%is ; ie = ha1%ie ; js = ha1%js ; je = ha1%je
387-
388-
allocate(tmp1(1:2*nc+1), source=0.0)
389-
allocate(tmp2(is:ie,js:je), source=0.0)
390-
allocate(FtFw(1:2*nc+1,1:2*nc+1), source=0.0)
391-
allocate(FtSSHw(is:ie,js:je,2*nc+1), source=0.0)
392-
393-
! Construct FtFw
394-
FtFw(:,:) = 0.0
395+
real :: tmp0 !< Temporary variable for Cholesky decomposition [nondim]
396+
real, dimension(2*nc+1,2*nc+1) :: L !< Lower triangular matrix of Cholesky decomposition [nondim]
397+
real, dimension(2*nc+1) :: tmp1 !< Inverse of the diagonal entries of L [nondim]
398+
real, dimension(ha1%is:ha1%ie,ha1%js:ha1%je) :: tmp2 !< 2D temporary array involving FtSSH [A]
399+
real, dimension(ha1%is:ha1%ie,ha1%js:ha1%je,2*nc+1) :: y !< 3D temporary array, i.e., L' * x [A]
400+
integer :: k, m, n
401+
402+
! Cholesky decomposition
395403
do m=1,2*nc+1
404+
405+
! First, calculate the diagonal entries
396406
tmp0 = 0.0
397-
do k=1,m-1
398-
tmp0 = tmp0 + FtFw(m,k) * FtFw(m,k)
407+
do k=1,m-1 ! This loop operates along the m-th row
408+
tmp0 = tmp0 + L(m,k) * L(m,k)
399409
enddo
400-
FtFw(m,m) = sqrt(FtF(m,m) - tmp0)
401-
tmp1(m) = 1 / FtFw(m,m)
402-
do k=m+1,2*nc+1
410+
L(m,m) = sqrt(FtF(m,m) - tmp0) ! This is the m-th diagonal entry
411+
412+
! Now calculate the off-diagonal entries
413+
tmp1(m) = 1 / L(m,m)
414+
do k=m+1,2*nc+1 ! This loop operates along the column below the m-th diagonal entry
403415
tmp0 = 0.0
404416
do n=1,m-1
405-
tmp0 = tmp0 + FtFw(k,n) * FtFw(m,n)
417+
tmp0 = tmp0 + L(k,n) * L(m,n)
406418
enddo
407-
FtFw(k,m) = (FtF(k,m) - tmp0) * tmp1(m)
419+
L(k,m) = (FtF(k,m) - tmp0) * tmp1(m) ! This is the k-th off-diagonal entry below the m-th diagonal entry
408420
enddo
409421
enddo
410422

411-
! Solve for (FtFw' * FtSSHw)
412-
FtSSHw(:,:,:) = ha1%FtSSH(:,:,:)
423+
! Solve for y from L * y = FtSSH
413424
do k=1,2*nc+1
414425
tmp2(:,:) = 0.0
415426
do m=1,k-1
416-
tmp2(:,:) = tmp2(:,:) + FtFw(k,m) * FtSSHw(:,:,m)
427+
tmp2(:,:) = tmp2(:,:) + L(k,m) * y(:,:,m)
417428
enddo
418-
FtSSHw(:,:,k) = (FtSSHw(:,:,k) - tmp2(:,:)) * tmp1(k)
429+
y(:,:,k) = (ha1%FtSSH(:,:,k) - tmp2(:,:)) * tmp1(k)
419430
enddo
420431

421-
! Solve for FtSSHw
432+
! Solve for x from L' * x = y
422433
do k=2*nc+1,1,-1
423434
tmp2(:,:) = 0.0
424435
do m=k+1,2*nc+1
425-
tmp2(:,:) = tmp2(:,:) + FtSSHw(:,:,m) * FtFw(m,k)
436+
tmp2(:,:) = tmp2(:,:) + L(m,k) * x(:,:,m)
426437
enddo
427-
FtSSHw(:,:,k) = (FtSSHw(:,:,k) - tmp2(:,:)) * tmp1(k)
438+
x(:,:,k) = (y(:,:,k) - tmp2(:,:)) * tmp1(k)
428439
enddo
429440

430-
deallocate(tmp1)
431-
deallocate(tmp2)
432-
deallocate(FtFw)
433-
434441
end subroutine HA_solver
435442

436443
!> \namespace harmonic_analysis
@@ -441,7 +448,7 @@ end subroutine HA_solver
441448
!! step, and x is a 2*nc-by-1 vector containing the constant coefficients of the sine/cosine for each constituent
442449
!! (i.e., the harmonic constants). At each grid point, the harmonic constants are computed using least squares,
443450
!!
444-
!! (F' * F) * x = F' * SSH_in,
451+
!! (F' * F) * x = F' * SSH_in, => FtF * x = FtSSH,
445452
!!
446453
!! where the prime denotes matrix transpose, and SSH_in is the sea surface height (or other fields) determined by
447454
!! the model. The dot products (F' * F) and (F' * SSH_in) are computed by accumulating the sums as the model is

0 commit comments

Comments
 (0)