MOM6
regrid_solvers.F90
1 !> Solvers of linear systems.
3 
4 ! This file is part of MOM6. See LICENSE.md for the license.
5 
6 use mom_error_handler, only : mom_error, fatal
7 
8 implicit none ; private
9 
10 public :: solve_linear_system, linear_solver, solve_tridiagonal_system, solve_diag_dominant_tridiag
11 
12 contains
13 
14 !> Solve the linear system AX = R by Gaussian elimination
15 !!
16 !! This routine uses Gauss's algorithm to transform the system's original
17 !! matrix into an upper triangular matrix. Back substitution yields the answer.
18 !! The matrix A must be square, with the first index varing down the column.
19 subroutine solve_linear_system( A, R, X, N, answers_2018 )
20  integer, intent(in) :: n !< The size of the system
21  real, dimension(N,N), intent(inout) :: a !< The matrix being inverted [nondim]
22  real, dimension(N), intent(inout) :: r !< system right-hand side [A]
23  real, dimension(N), intent(inout) :: x !< solution vector [A]
24  logical, optional, intent(in) :: answers_2018 !< If true or absent use older, less efficient expressions.
25  ! Local variables
26  real, parameter :: eps = 0.0 ! Minimum pivot magnitude allowed
27  real :: factor ! The factor that eliminates the leading nonzero element in a row.
28  real :: pivot, i_pivot ! The pivot value and its reciprocal [nondim]
29  real :: swap_a, swap_b
30  logical :: found_pivot ! If true, a pivot has been found
31  logical :: old_answers ! If true, use expressions that give the original (2008 through 2018) MOM6 answers
32  integer :: i, j, k
33 
34  old_answers = .true. ; if (present(answers_2018)) old_answers = answers_2018
35 
36  ! Loop on rows to transform the problem into multiplication by an upper-right matrix.
37  do i = 1,n-1
38 
39 
40  ! Start to look for a pivot in the current row, i. If the pivot in row i is not valid,
41  ! keep looking for a valid pivot by searching the entries of column i in rows below row i.
42  ! Once a valid pivot is found (say in row k), rows i and k are swaped.
43  found_pivot = .false.
44  k = i
45  do while ( ( .NOT. found_pivot ) .AND. ( k <= n ) )
46  if ( abs( a(k,i) ) > eps ) then ! A valid pivot has been found
47  found_pivot = .true.
48  else ! Seek a valid pivot in the next row
49  k = k + 1
50  endif
51  enddo ! end loop to find pivot
52 
53  ! If no pivot could be found, the system is singular.
54  if ( .NOT. found_pivot ) then
55  write(0,*) ' A=',a
56  call mom_error( fatal, 'The linear system is singular !' )
57  endif
58 
59  ! If the pivot is in a row that is different than row i, that is if
60  ! k is different than i, we need to swap those two rows
61  if ( k /= i ) then
62  do j = 1,n
63  swap_a = a(i,j) ; a(i,j) = a(k,j) ; a(k,j) = swap_a
64  enddo
65  swap_b = r(i) ; r(i) = r(k) ; r(k) = swap_b
66  endif
67 
68  ! Transform pivot to 1 by dividing the entire row (right-hand side included) by the pivot
69  if (old_answers) then
70  pivot = a(i,i)
71  do j = i,n ; a(i,j) = a(i,j) / pivot ; enddo
72  r(i) = r(i) / pivot
73  else
74  i_pivot = 1.0 / a(i,i)
75  a(i,i) = 1.0
76  do j = i+1,n ; a(i,j) = a(i,j) * i_pivot ; enddo
77  r(i) = r(i) * i_pivot
78  endif
79 
80  ! #INV: At this point, A(i,i) is a suitable pivot and it is equal to 1
81 
82  ! Put zeros in column for all rows below that contain the pivot (which is row i)
83  do k = i+1,n ! k is the row index
84  factor = a(k,i)
85  ! A(k,i) = 0.0 ! These elements are not used again, so this line can be skipped for speed.
86  do j = i+1,n ! j is the column index
87  a(k,j) = a(k,j) - factor * a(i,j)
88  enddo
89  r(k) = r(k) - factor * r(i)
90  enddo
91 
92  enddo ! end loop on i
93 
94  ! Solve system by back substituting in what is now an upper-right matrix.
95  x(n) = r(n) / a(n,n) ! The last row is now trivially solved.
96  do i = n-1,1,-1 ! loop on rows, starting from second to last row
97  x(i) = r(i)
98  do j = i+1,n
99  x(i) = x(i) - a(i,j) * x(j)
100  enddo
101  if (old_answers) x(i) = x(i) / a(i,i)
102  enddo
103 
104 end subroutine solve_linear_system
105 
106 !> Solve the linear system AX = R by Gaussian elimination
107 !!
108 !! This routine uses Gauss's algorithm to transform the system's original
109 !! matrix into an upper triangular matrix. Back substitution then yields the answer.
110 !! The matrix A must be square, with the first index varing along the row.
111 subroutine linear_solver( N, A, R, X )
112  integer, intent(in) :: n !< The size of the system
113  real, dimension(N,N), intent(inout) :: a !< The matrix being inverted [nondim]
114  real, dimension(N), intent(inout) :: r !< system right-hand side [A]
115  real, dimension(N), intent(inout) :: x !< solution vector [A]
116 
117  ! Local variables
118  real, parameter :: eps = 0.0 ! Minimum pivot magnitude allowed
119  real :: factor ! The factor that eliminates the leading nonzero element in a row.
120  real :: i_pivot ! The reciprocal of the pivot value [inverse of the input units of a row of A]
121  real :: swap
122  logical :: found_pivot ! If true, a pivot has been found
123  integer :: i, j, k
124 
125  ! Loop on rows to transform the problem into multiplication by an upper-right matrix.
126  do i=1,n-1
127  ! Seek a pivot for column i starting in row i, and continuing into the remaining rows. If the
128  ! pivot is in a row other than i, swap them. If no valid pivot is found, i = N+1 after this loop.
129  do k=i,n ; if ( abs( a(i,k) ) > eps ) exit ; enddo ! end loop to find pivot
130  if ( k > n ) then ! No pivot could be found and the system is singular.
131  write(0,*) ' A=',a
132  call mom_error( fatal, 'The linear system is singular !' )
133  endif
134 
135  ! If the pivot is in a row that is different than row i, swap those two rows, noting that both
136  ! rows start with i-1 zero values.
137  if ( k /= i ) then
138  do j=i,n ; swap = a(j,i) ; a(j,i) = a(j,k) ; a(j,k) = swap ; enddo
139  swap = r(i) ; r(i) = r(k) ; r(k) = swap
140  endif
141 
142  ! Transform the pivot to 1 by dividing the entire row (right-hand side included) by the pivot
143  i_pivot = 1.0 / a(i,i)
144  a(i,i) = 1.0
145  do j=i+1,n ; a(j,i) = a(j,i) * i_pivot ; enddo
146  r(i) = r(i) * i_pivot
147 
148  ! Put zeros in column for all rows below that contain the pivot (which is row i)
149  do k=i+1,n ! k is the row index
150  factor = a(i,k)
151  ! A(i,k) = 0.0 ! These elements are not used again, so this line can be skipped for speed.
152  do j=i+1,n ; a(j,k) = a(j,k) - factor * a(j,i) ; enddo
153  r(k) = r(k) - factor * r(i)
154  enddo
155 
156  enddo ! end loop on i
157 
158  ! Solve the system by back substituting into what is now an upper-right matrix.
159  x(n) = r(n) / a(n,n) ! The last row is now trivially solved.
160  do i=n-1,1,-1 ! loop on rows, starting from second to last row
161  x(i) = r(i)
162  do j=i+1,n ; x(i) = x(i) - a(j,i) * x(j) ; enddo
163  enddo
164 
165 end subroutine linear_solver
166 
167 
168 !> Solve the tridiagonal system AX = R
169 !!
170 !! This routine uses Thomas's algorithm to solve the tridiagonal system AX = R.
171 !! (A is made up of lower, middle and upper diagonals)
172 subroutine solve_tridiagonal_system( Al, Ad, Au, R, X, N, answers_2018 )
173  integer, intent(in) :: n !< The size of the system
174  real, dimension(N), intent(in) :: ad !< Matrix center diagonal
175  real, dimension(N), intent(in) :: al !< Matrix lower diagonal
176  real, dimension(N), intent(in) :: au !< Matrix upper diagonal
177  real, dimension(N), intent(in) :: r !< system right-hand side
178  real, dimension(N), intent(out) :: x !< solution vector
179  logical, optional, intent(in) :: answers_2018 !< If true use older, less acccurate expressions.
180  ! Local variables
181  real, dimension(N) :: pivot, al_piv
182  real, dimension(N) :: c1 ! Au / pivot for the backward sweep
183  real :: i_pivot ! The inverse of the most recent pivot
184  integer :: k ! Loop index
185  logical :: old_answers ! If true, use expressions that give the original (2008 through 2018) MOM6 answers
186 
187  old_answers = .true. ; if (present(answers_2018)) old_answers = answers_2018
188 
189  if (old_answers) then
190  ! This version gives the same answers as the original (2008 through 2018) MOM6 code
191  ! Factorization and forward sweep
192  pivot(1) = ad(1)
193  x(1) = r(1)
194  do k = 2,n
195  al_piv(k) = al(k) / pivot(k-1)
196  pivot(k) = ad(k) - al_piv(k) * au(k-1)
197  x(k) = r(k) - al_piv(k) * x(k-1)
198  enddo
199 
200  ! Backward sweep
201  x(n) = r(n) / pivot(n) ! This should be X(N) / pivot(N), but is OK if Al(N) = 0.
202  do k = n-1,1,-1
203  x(k) = ( x(k) - au(k)*x(k+1) ) / pivot(k)
204  enddo
205  else
206  ! This is a more typical implementation of a tridiagonal solver than the one above.
207  ! It is mathematically equivalent but differs at roundoff, which can cascade up to larger values.
208 
209  ! Factorization and forward sweep
210  i_pivot = 1.0 / ad(1)
211  x(1) = r(1) * i_pivot
212  do k = 2,n
213  c1(k-1) = au(k-1) * i_pivot
214  i_pivot = 1.0 / (ad(k) - al(k) * c1(k-1))
215  x(k) = (r(k) - al(k) * x(k-1)) * i_pivot
216  enddo
217  ! Backward sweep
218  do k = n-1,1,-1
219  x(k) = x(k) - c1(k) * x(k+1)
220  enddo
221 
222  endif
223 
224 end subroutine solve_tridiagonal_system
225 
226 
227 !> Solve the tridiagonal system AX = R
228 !!
229 !! This routine uses a variant of Thomas's algorithm to solve the tridiagonal system AX = R, in
230 !! a form that is guaranteed to avoid dividing by a zero pivot. The matrix A is made up of
231 !! lower (Al) and upper diagonals (Au) and a central diagonal Ad = Ac+Al+Au, where
232 !! Al, Au, and Ac are all positive (or negative) definite. However when Ac is smaller than
233 !! roundoff compared with (Al+Au), the answers are prone to inaccuracy.
234 subroutine solve_diag_dominant_tridiag( Al, Ac, Au, R, X, N )
235  integer, intent(in) :: n !< The size of the system
236  real, dimension(N), intent(in) :: ac !< Matrix center diagonal offset from Al + Au
237  real, dimension(N), intent(in) :: al !< Matrix lower diagonal
238  real, dimension(N), intent(in) :: au !< Matrix upper diagonal
239  real, dimension(N), intent(in) :: r !< system right-hand side
240  real, dimension(N), intent(out) :: x !< solution vector
241  ! Local variables
242  real, dimension(N) :: c1 ! Au / pivot for the backward sweep
243  real :: d1 ! The next value of 1.0 - c1
244  real :: i_pivot ! The inverse of the most recent pivot
245  real :: denom_t1 ! The first term in the denominator of the inverse of the pivot.
246  integer :: k ! Loop index
247 
248  ! Factorization and forward sweep, in a form that will never give a division by a
249  ! zero pivot for positive definite Ac, Al, and Au.
250  i_pivot = 1.0 / (ac(1) + au(1))
251  d1 = ac(1) * i_pivot
252  c1(1) = au(1) * i_pivot
253  x(1) = r(1) * i_pivot
254  do k=2,n-1
255  denom_t1 = ac(k) + d1 * al(k)
256  i_pivot = 1.0 / (denom_t1 + au(k))
257  d1 = denom_t1 * i_pivot
258  c1(k) = au(k) * i_pivot
259  x(k) = (r(k) - al(k) * x(k-1)) * i_pivot
260  enddo
261  i_pivot = 1.0 / (ac(n) + d1 * al(n))
262  x(n) = (r(n) - al(n) * x(n-1)) * i_pivot
263  ! Backward sweep
264  do k=n-1,1,-1
265  x(k) = x(k) - c1(k) * x(k+1)
266  enddo
267 
268 end subroutine solve_diag_dominant_tridiag
269 
270 
271 !> \namespace regrid_solvers
272 !!
273 !! Date of creation: 2008.06.12
274 !! L. White
275 !!
276 !! This module contains solvers of linear systems.
277 !! These routines have now been updated for greater efficiency, especially in special cases.
278 
279 end module regrid_solvers
Solvers of linear systems.
Routines for error handling and I/O management.