MODFLOW 6  version 6.7.0.dev2
USGS Modular Hydrologic Model
svdmodule Module Reference

Functions/Subroutines

pure real(dp) function, dimension(:, :), allocatable house_holder_matrix (x)
 
pure subroutine, public bidiagonal_decomposition (A, P, Qt)
 
pure real(dp) function, dimension(2, 2) givens_rotation (a, b)
 
pure real(dp) function compute_shift (A)
 
pure subroutine, public bidiagonal_qr_decomposition (A, U, VT)
 
pure subroutine handle_zero_diagonal (A, U, VT)
 
pure real(dp) function superdiagonal_norm (A)
 
pure subroutine find_nonzero_superdiagonal (A, p, q)
 
pure logical(lgp) function has_zero_diagonal (A)
 
pure subroutine make_matrix_square (A, Qt)
 
pure subroutine clean_superdiagonal (A)
 
pure subroutine, public svd (A, U, S, VT)
 Singular Value Decomposition. More...
 

Function/Subroutine Documentation

◆ bidiagonal_decomposition()

pure subroutine, public svdmodule::bidiagonal_decomposition ( real(dp), dimension(:, :), intent(inout)  A,
real(dp), dimension(:, :), intent(out), allocatable  P,
real(dp), dimension(:, :), intent(out), allocatable  Qt 
)

Definition at line 64 of file SingularValueDecomposition.f90.

65  ! -- dummy
66  real(DP), intent(inout), dimension(:, :) :: A
67  real(DP), intent(out), dimension(:, :), allocatable :: P, Qt
68  ! -- locals
69  integer(I4B) :: M, N
70  integer(I4B) :: I
71  real(DP), allocatable, dimension(:, :) :: Qi, Pi
72  real(DP), allocatable, dimension(:) :: h
73 
74  m = size(a, dim=1) ! Number of rows
75  n = size(a, dim=2) ! Number of columns
76 
77  qt = eye(n)
78  p = eye(m)
79 
80  do i = 1, min(m, n)
81  ! columns
82  h = a(i:m, i)
83  pi = house_holder_matrix(h)
84  a(i:m, :) = matmul(pi, a(i:m, :)) ! Apply householder transformation from left
85  p(:, i:m) = matmul(p(:, i:m), pi)
86 
87  ! rows
88  if (i < n) then
89  h = a(i, i + 1:n)
90  qi = transpose(house_holder_matrix(h))
91  a(:, i + 1:n) = matmul(a(:, i + 1:n), qi) ! Apply householder transformation from right
92  qt(i + 1:n, :) = matmul(qi, qt(i + 1:n, :))
93  end if
94 
95  end do
96 
Here is the call graph for this function:
Here is the caller graph for this function:

◆ bidiagonal_qr_decomposition()

pure subroutine, public svdmodule::bidiagonal_qr_decomposition ( real(dp), dimension(:, :), intent(inout)  A,
real(dp), dimension(:, :), intent(inout)  U,
real(dp), dimension(:, :), intent(inout)  VT 
)

Definition at line 183 of file SingularValueDecomposition.f90.

184  ! -- dummy
185  real(DP), intent(inout), dimension(:, :) :: A
186  real(DP), intent(inout), dimension(:, :) :: U, Vt
187  ! -- locals
188  integer(I4B) :: m, n, I, J
189  real(DP), dimension(2, 2) :: G
190  real(DP) :: t11, t12, mu
191 
192  m = size(a, dim=1) ! Number of rows
193  n = size(a, dim=2) ! Number of columns
194 
195  DO i = 1, n - 1
196  j = i + 1
197  if (i == 1) then
198  ! For the first iteration use the implicit shift
199  mu = compute_shift(a)
200  ! Apply the shift to the full tri-diagonal matrix.
201  t11 = a(1, 1)**2 - mu
202  t12 = a(1, 1) * a(1, 2)
203 
204  g = givens_rotation(t11, t12)
205  else
206  g = givens_rotation(a(i - 1, i), a(i - 1, j))
207  end if
208 
209  a(:, i:j) = matmul(a(:, i:j), transpose(g))
210  vt(i:j, :) = matmul(g, vt(i:j, :))
211 
212  if (j > m) cycle
213  g = givens_rotation(a(i, i), a(j, i))
214  a(i:j, :) = matmul(g, a(i:j, :))
215  u(:, i:j) = matmul(u(:, i:j), transpose(g))
216  END DO
217 
Here is the call graph for this function:
Here is the caller graph for this function:

◆ clean_superdiagonal()

pure subroutine svdmodule::clean_superdiagonal ( real(dp), dimension(:, :), intent(inout)  A)
private

Definition at line 385 of file SingularValueDecomposition.f90.

386  ! -- dummy
387  real(DP), intent(inout), dimension(:, :) :: A
388  ! -- locals
389  integer(I4B) :: n, m, j
390 
391  m = size(a, dim=1) ! Number of rows
392  n = size(a, dim=2) ! Number of columns
393 
394  do j = 1, min(n, m) - 1
395  if (abs(a(j, j + 1)) <= dprec * (abs(a(j, j)) + abs(a(j + 1, j + 1)))) then
396  a(j, j + 1) = 0.0_dp
397  end if
398  end do
399 
Here is the caller graph for this function:

◆ compute_shift()

pure real(dp) function svdmodule::compute_shift ( real(dp), dimension(:, :), intent(in)  A)
private

Definition at line 135 of file SingularValueDecomposition.f90.

136  ! -- dummy
137  real(DP), intent(in), dimension(:, :) :: A
138  real(DP) :: mu
139  ! -- locals
140  integer(I4B) :: m, n, min_mn
141  real(DP) T11, T12, T21, T22
142  real(DP) dm, fmmin, fm, dn
143  real(DP) :: mean, product, mean_product, mu1, mu2
144 
145  m = size(a, dim=1) ! Number of rows
146  n = size(a, dim=2) ! Number of columns
147 
148  min_mn = min(m, n)
149 
150  dn = a(min_mn, min_mn)
151  dm = a(min_mn - 1, min_mn - 1)
152  fm = a(min_mn - 1, min_mn)
153  if (min_mn > 2) then
154  fmmin = a(min_mn - 2, min_mn - 1)
155  else
156  fmmin = 0.0_dp
157  end if
158 
159  t11 = dm**2 + fmmin**2
160  t12 = dm * fm
161  t21 = t12
162  t22 = fm**2 + dn**2
163 
164  mean = (t11 + t22) / 2.0_dp
165  product = t11 * t22 - t12 * t21
166  mean_product = mean**2 - product
167  if (abs(mean_product) < dem6) then
168  mean_product = 0.0_dp
169  end if
170  mu1 = mean - sqrt(mean_product)
171  mu2 = mean + sqrt(mean_product)
172  if (abs(t22 - mu1) < abs(t22 - mu2)) then
173  mu = mu1
174  else
175  mu = mu2
176  end if
177 
Here is the caller graph for this function:

◆ find_nonzero_superdiagonal()

pure subroutine svdmodule::find_nonzero_superdiagonal ( real(dp), dimension(:, :), intent(in)  A,
integer(i4b), intent(out)  p,
integer(i4b), intent(out)  q 
)
private

Definition at line 296 of file SingularValueDecomposition.f90.

297  ! -- dummy
298  real(DP), intent(in), dimension(:, :) :: A
299  integer(I4B), intent(out) :: p ! Index of the first nonzero superdiagonal (start of active block)
300  integer(I4B), intent(out) :: q ! Index of the last nonzero superdiagonal (end of active block)
301  ! -- locals
302  integer(I4B) :: m, n, j, min_mn
303 
304  m = size(a, dim=1) ! Number of rows
305  n = size(a, dim=2) ! Number of columns
306 
307  min_mn = min(m, n)
308  p = 1
309  q = min_mn
310 
311  do j = min_mn, 2, -1
312  if (abs(a(j - 1, j)) > dprec) then
313  q = j
314  exit
315  end if
316  end do
317 
318  do j = q - 1, 2, -1
319  if (abs(a(j - 1, j)) < dprec) then
320  p = j
321  exit
322  end if
323  end do
Here is the caller graph for this function:

◆ givens_rotation()

pure real(dp) function, dimension(2, 2) svdmodule::givens_rotation ( real(dp), intent(in)  a,
real(dp), intent(in)  b 
)
private

Definition at line 102 of file SingularValueDecomposition.f90.

103  ! -- dummy
104  real(DP), intent(in) :: a, b
105  real(DP), dimension(2, 2) :: G
106  ! -- locals
107  real(DP) :: c, s, h, d
108 
109  if (abs(b) < dprec) then
110  g = eye(2)
111  return
112  end if
113 
114  h = hypot(a, b)
115  d = 1.0 / h
116  c = abs(a) * d
117  s = sign(d, a) * b
118 
119  g(1, 1) = c
120  g(1, 2) = s
121  g(2, 1) = -s
122  g(2, 2) = c
123 
Here is the call graph for this function:
Here is the caller graph for this function:

◆ handle_zero_diagonal()

pure subroutine svdmodule::handle_zero_diagonal ( real(dp), dimension(:, :), intent(inout)  A,
real(dp), dimension(:, :), intent(inout)  U,
real(dp), dimension(:, :), intent(inout)  VT 
)
private

Definition at line 230 of file SingularValueDecomposition.f90.

231  ! -- dummy
232  real(DP), intent(inout), dimension(:, :) :: A
233  real(DP), intent(inout), dimension(:, :) :: U, Vt
234  ! -- locals
235  integer(I4B) :: m, n, I
236  real(DP), dimension(2, 2) :: G
237  integer(I4B) :: zero_index
238 
239  m = size(a, dim=1) ! Number of rows
240  n = size(a, dim=2) ! Number of columns
241 
242  do i = 1, min(m, n)
243  if (abs(a(i, i)) < dprec) then
244  zero_index = i
245  exit
246  end if
247  end do
248 
249  if (zero_index == min(n, m)) then
250  ! If the zero index is the last element of the diagonal then zero out the column
251  do i = zero_index - 1, 1, -1
252  g = givens_rotation(a(i, i), a(i, zero_index))
253  a(:, [i, zero_index]) = matmul(a(:, [i, zero_index]), transpose(g))
254  vt([i, zero_index], :) = matmul(g, vt([i, zero_index], :))
255  end do
256  else
257  ! Else zero out the row
258  do i = zero_index + 1, n
259  g = givens_rotation(a(i, i), a(zero_index, i))
260  a([zero_index, i], :) = matmul(transpose(g), a([zero_index, i], :))
261  u(:, [zero_index, i]) = matmul(u(:, [zero_index, i]), g)
262  end do
263  end if
Here is the call graph for this function:
Here is the caller graph for this function:

◆ has_zero_diagonal()

pure logical(lgp) function svdmodule::has_zero_diagonal ( real(dp), dimension(:, :), intent(in)  A)
private

Definition at line 329 of file SingularValueDecomposition.f90.

330  ! -- dummy
331  real(DP), intent(in) :: A(:, :)
332  logical(LGP) :: has_zero
333  ! -- locals
334  integer(I4B) :: m, n, I
335 
336  m = size(a, dim=1) ! Number of rows
337  n = size(a, dim=2) ! Number of columns
338 
339  has_zero = .false.
340  do i = 1, min(m, n)
341  if (abs(a(i, i)) < dprec) then
342  has_zero = .true.
343  exit
344  end if
345  end do
346 
Here is the caller graph for this function:

◆ house_holder_matrix()

pure real(dp) function, dimension(:, :), allocatable svdmodule::house_holder_matrix ( real(dp), dimension(:), intent(in)  x)
private

Definition at line 18 of file SingularValueDecomposition.f90.

19  ! -- dummy
20  real(DP), intent(in) :: x(:)
21  real(DP), allocatable, dimension(:, :) :: Q
22  ! -- locals
23  real(DP) :: x_norm, y_norm
24  real(DP), allocatable :: v(:)
25  real(DP), allocatable :: w(:)
26 
27  x_norm = norm2(x)
28  y_norm = norm2(x(2:))
29 
30  q = eye(size(x))
31 
32  if (dabs(y_norm) < dsame) then
33  return
34  end if
35 
36  v = x
37  if (x(1) > 0) then
38  ! Parlett (1971) suggested this modification to avoid cancellation
39  v(1) = -(y_norm**2) / (x(1) + x_norm)
40  else
41  v(1) = v(1) - x_norm
42  end if
43 
44  w = v / norm2(v)
45 
46  q = q - 2.0_dp * outer_product(w, w)
47 
Here is the call graph for this function:
Here is the caller graph for this function:

◆ make_matrix_square()

pure subroutine svdmodule::make_matrix_square ( real(dp), dimension(:, :), intent(inout)  A,
real(dp), dimension(:, :), intent(inout), allocatable  Qt 
)
private

Definition at line 355 of file SingularValueDecomposition.f90.

356  ! -- dummy
357  real(DP), intent(inout), dimension(:, :) :: A
358  real(DP), intent(inout), dimension(:, :), allocatable :: Qt
359  ! -- locals
360  real(DP), dimension(2, 2) :: G
361  integer(I4B) :: m, n, I
362 
363  m = size(a, dim=1) ! Number of rows
364  n = size(a, dim=2) ! Number of columns
365 
366  do i = m, 1, -1
367  g = givens_rotation(a(i, i), a(i, m + 1))
368  a(:, [i, m + 1]) = matmul(a(:, [i, m + 1]), transpose(g))
369  qt([i, m + 1], :) = matmul(g, qt([i, m + 1], :))
370  end do
371 
Here is the call graph for this function:
Here is the caller graph for this function:

◆ superdiagonal_norm()

pure real(dp) function svdmodule::superdiagonal_norm ( real(dp), dimension(:, :), intent(in)  A)
private

Definition at line 269 of file SingularValueDecomposition.f90.

270  ! -- dummy
271  real(DP), intent(in) :: A(:, :)
272  real(DP) :: norm
273  ! -- locals
274  integer(I4B) :: m, n, I
275 
276  m = size(a, dim=1) ! Number of rows
277  n = size(a, dim=2) ! Number of columns
278 
279  norm = 0.0_dp
280  do i = 1, min(m, n) - 1
281  norm = max(norm, abs(a(i, i + 1)))
282  end do
283 
Here is the caller graph for this function:

◆ svd()

pure subroutine, public svdmodule::svd ( real(dp), dimension(:, :), intent(in)  A,
real(dp), dimension(:, :), intent(out), allocatable  U,
real(dp), dimension(:, :), intent(out), allocatable  S,
real(dp), dimension(:, :), intent(out), allocatable  VT 
)

This method decomposes the matrix A into U, S and VT. It follows the algorithm as described by Golub and Reinsch.

The first step is to decompose the matrix A into a bidiagonal matrix. This is done using Householder transformations. Then second step is to decompose the bidiagonal matrix into U, S and VT by repetitively applying the QR algorithm. If there is a zero on the diagonal or superdiagonal the matrix can be split into two smaller matrices and the QR algorithm can be applied to the smaller matrices.

The matrix U is the eigenvectors of A*A^T The matrix VT is the eigenvectors of A^T*A The matrix S is the square root of the eigenvalues of A*A^T or A^T*A

Definition at line 420 of file SingularValueDecomposition.f90.

421  ! -- dummy
422  real(DP), intent(in), dimension(:, :) :: A
423  real(DP), intent(out), dimension(:, :), allocatable :: U
424  real(DP), intent(out), dimension(:, :), allocatable :: S
425  real(DP), intent(out), dimension(:, :), allocatable :: VT
426  ! -- locals
427  integer(I4B) :: i ! Loop counter for QR iterations
428  integer(I4B) :: max_itr ! Maximum number of QR iterations allowed for convergence
429  real(DP) :: error ! Convergence criterion (superdiagonal norm)
430 
431  integer(I4B) :: m ! Number of rows in input matrix A
432  integer(I4B) :: n ! Number of columns in input matrix A
433 
434  integer(I4B) :: r ! Start index of active submatrix (for QR step)
435  integer(I4B) :: q ! End index of active submatrix (for QR step)
436 
437  max_itr = 100
438 
439  m = size(a, dim=1) ! Number of rows
440  n = size(a, dim=2) ! Number of columns
441  s = a
442 
443  call bidiagonal_decomposition(s, u, vt)
444 
445  if (n > m) then
446  call make_matrix_square(s, vt)
447  end if
448 
449  do i = 1, max_itr
450  call find_nonzero_superdiagonal(s, r, q)
451 
452  ! find zero diagonal
453  if (has_zero_diagonal(s(r:q, r:q))) then
454  ! write(*,*) 'Iteration: ', i, ' handle zero diagonal element'
455  call handle_zero_diagonal(s(r:q, r:q), u(:, r:q), vt(r:q, :))
456  else
457  call bidiagonal_qr_decomposition(s(r:q, r:q), u(:, r:q), vt(r:q, :))
458  call clean_superdiagonal(s(r:q, r:q))
459 
460  error = superdiagonal_norm(s)
461  ! write(*,*) 'Iteration: ', i, ' Error: ', error
462  if (error < dprec) exit
463  end if
464  end do
465 
Here is the call graph for this function:
Here is the caller graph for this function: