mcholdc.pro
10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
;+
; NAME:
; MCHOLDC
;
; AUTHOR:
; Craig B. Markwardt, NASA/GSFC Code 662, Greenbelt, MD 20770
; craigm@lheamail.gsfc.nasa.gov
;
; PURPOSE:
; Modified Cholesky Factorization of a Symmetric Matrix
;
; MAJOR TOPICS:
; Linear Systems
;
; CALLING SEQUENCE:
; MCHOLDC, A, D, E [, /OUTFULL, /SPARSE, /PIVOT, TAU=TAU, $
; PERMUTE=PERMUTE, INVPERMUTE=INVPERMUTE ]
;
; DESCRIPTION:
;
; Given a symmetric matrix A, the MCHOLDC procedure computes the
; factorization:
;
; A + E = TRANSPOSE(U) ## D ## U
;
; where A is the original matrix (optionally permuted if the PIVOT
; keyword is set), U is an upper triangular matrix, D is a diagonal
; matrix, and E is a diagonal error matrix.
;
; The standard Cholesky factorization is only defined for a positive
; definite symmetric matrix. If the input matrix is positive
; definite then the error term E will be zero upon output. The user
; may in fact test the positive-definiteness of their matrix by
; factoring it and testing that all terms in E are zero.
;
; If A is *not* positive definite, then the standard Cholesky
; factorization is undefined. In that case we adopt the "modified"
; factorization strategy of Gill, Murray and Wright (p. 108), which
; involves adding a diagonal error term to A in order to enforce
; positive-definiteness. The approach is optimal in the sense that
; it attempts to minimize E, and thus disturbing A as little as
; possible. For optimization problems, this approximate
; factorization can be used to find a direction of descent even when
; the curvature is not positive definite.
;
; The upper triangle of A is modified in place. By default, the
; lower triangle is left unchanged, and the matrices D and E are
; actually returned as vectors containing only the diagonal terms.
; However, if the keyword OUTFULL is set then full matrices are
; returned. This is useful when matrix multiplication will be
; performed at the next step.
;
; The modified Cholesky factorization is most stable when pivoting is
; performed. If the keyword PIVOT is set, then pivoting is performed
; to place the diagonal terms with the largest amplitude in the next
; row. The permutation vectors returned in PERMUTE and INVPERMUTE
; can be used to apply and reverse the pivoting.
; [ i.e., (U(PP,*))(*,PP) applies the permutation and
; (U(IPP,*))(*,IPP) reverses it, where PP and IPP are the
; permutation and inverse permutation vectors. ]
;
; If the matrix to be factored is very sparse, then setting the
; SPARSE keyword may improve the speed of the computations. SPARSE
; is more costly on a dense matrix, but only grows as N^2, where as
; the standard computation grows as N^3, where N is the rank of the
; matrix.
;
; If the CHOLSOL keyword is set, then the output is slightly
; modified. The returned matrix A that is returned is structured so
; that it is compatible with the CHOLSOL built-in IDL routine. This
; involves converting A to being upper to lower triangular, and
; multiplying by SQRT(D). Users must be sure to check that all
; elements of E are zero before using CHOLSOL.
;
; PARAMETERS:
;
; A - upon input, a symmetric NxN matrix to be factored.
; Upon output, the upper triangle of the matrix is modified to
; contain the factorization.
;
; D - upon output, the diagonal matrix D.
;
; E - upon output, the diagonal error matrix E.
;
; KEYWORD PARAMETERS:
;
; OUTFULL - if set, then A, D and E will be modified to be full IDL
; matrices than can be matrix-multiplied. By default,
; only the upper triangle of A is modified, and D and E
; are returned as vectors.
;
; PIVOT - if set, then diagonal elements of A are pivoted into place
; and operated on, in decrease order of their amplitude.
; The permutation vectors are returned in the PERMUTE and
; INVPERMUTE keywords.
;
; PERMUTE - upon return, the permutation vector which converts a
; vector into permuted form.
;
; INVPERMUTE - upon return, the inverse permutation vector which
; converts a vector from permuted form back into
; standard form.
;
; SPARSE - if set, then operations optimized for sparse matrices are
; employed. For large but very sparse matrices, this can
; save a significant amount of computation time.
;
; CHOLSOL - if set, then A and D are returned, suitable for input to
; the built-in IDL routine CHOLSOL. CHOLSOL is mutually
; exclusive with the FULL keyword.
;
; TAU - if set, then use the Tau factor as described in the
; "unconventional" modified Cholesky factorization, as
; described by Xie & Schlick.
; Default: the unconventional technique is not used.
;
; EXAMPLE:
;
; Example 1
; ---------
; a = randomn(seed, 5,5) ;; Generate a random matrix
; a = 0.5*(transpose(a)+a) ;; Symmetrize it
;
; a1 = a ;; Make a copy
; mcholdc, a1, d, e, /full ;; Factorize it
; print, max(abs(e)) ;; This matrix is not positive definite
;
; diff = transpose(a1) ## d ## a1 - e - a
; ;; Test the factorization by inverting
; ;; it and subtracting A
; print, max(abs(diff)) ;; Differences are small
;
; Example 2
; ---------
; Solving a problem with MCHOLDC and CHOLSOL
;
; a = [[6E,15,55],[15E,55,225],[55E,225,979]]
; b = [9.5E,50,237]
;
; mcholdc, a, d, e, /cholsol ;; Factorize matrix, compatible w/ CHOLSOL
; if total(abs(e)) NE 0 then $
; message, 'ERROR: Matrix A is not positive definite'
;
; x = cholsol(a, d, b) ;; Solve with CHOLSOL
; print, x
; -0.500001 -0.999999 0.500000
; which is within 1e-6 of the true solution.
;
;
; REFERENCES:
;
; Gill, P. E., Murray, W., & Wright, M. H. 1981
; *Practical Optimization*, Academic Press
; Schlick, T. & Fogelson, A., "TNPACK - A Truncated Newton
; Minimization Package for Large- Scale Problems: I. Algorithm and
; Usage," 1992, ACM TOMS, v. 18, p. 46-70. (Alg. 702)
; Xie, D. & Schlick, T., "Remark on Algorithm 702 - The Updated
; Truncated Newton Minimization Package," 1999, ACM TOMS, v. 25,
; p. 108-122.
;
; MODIFICATION HISTORY:
; Written, CM, Apr 2001
; Added CHOLSOL keyword, CM, 15 Feb 2002
; Fix bug when computing final correction factor (thanks to Aaron
; Adcock), CM, 13 Nov 2010
;
; $Id: mcholdc.pro,v 1.6 2010/11/13 09:45:55 cmarkwar Exp $
;
;-
; Copyright (C) 2001, 2002, 2010, Craig Markwardt
; This software is provided as is without any warranty whatsoever.
; Permission to use, copy, modify, and distribute modified or
; unmodified copies is granted, provided this copyright and disclaimer
; are included unchanged.
;-
pro mcholdc, a, d, e, outfull=full, sparse=sparse, pivot=pivot, $
permute=pp, invpermute=ipp, tau=tau0, cholsol=cholsol
if n_params() EQ 0 then begin
message, 'USAGE: MCHOLDC, A, D, E [, /OUTFULL, /SPARSE, /PIVOT, '+$
'/CHOLSOL, TAU=, PERMUTE=, INVPERMUTE= ]', /info
return
endif
;; Test for proper dimensions
sz = size(a)
if sz(0) EQ 0 then begin
d = a
a = a(0)*0 + 1
e = a(0)*0
return
endif
if sz(0) NE 2 then $
message, 'ERROR: Matrix A must be two dimensional'
if sz(1) NE sz(2) then $
message, 'ERROR: Matrix A must be square'
if n_elements(tau0) GT 0 then tau = tau0(0)
n = sz(1)
zero = a(0)*0
one = zero + 1
eps = zero + 1e-6
;; Gamma and Xi are the max diagonal and off-diagonal components
diag = lindgen(n)*n + lindgen(n)
gamma = max(abs(a(diag)))
xi = zero
for i = 0L, n-2 do begin
xi = max( [xi, max(abs(a(i+1:*,i)))] )
endfor
eps1 = max([gamma,xi]) * eps
del = max([eps, eps1])
;; Compute the bound on the diagonal elements
bound = max([gamma, xi/sqrt(n^2-1), eps])
;; Compute output arrays
d = replicate(zero, n)
e = d
pp = lindgen(n)
for j = 0, n-1 do begin
;; Pivoting - search for max element of abs(diag(A))
if keyword_set(pivot) then begin
maxa = max(abs(a(diag(j:*))), jmax)
if jmax GT 0 then begin
jmax = jmax + j
nn = lindgen(n)
j1 = (nn*n+j ) < (nn+j*n)
j2 = (nn*n+jmax) < (nn+jmax*n)
temp = j1(j) & j1(j) = j1(jmax) & j1(jmax) = temp
;; Exchange the row/columns
temp = a(j1) & a(j1) = a(j2) & a(j2) = temp
;; Change the permutation vector
temp = pp(j) & pp(j) = pp(jmax) & pp(jmax) = temp
endif
endif
;; Compute update to the L matrix, in place
if j GT 0 AND j LT n-1 then begin
;; The sparse path computes the same thing, but is faster if
;; very few components of the matrix are non-zero.
if keyword_set(sparse) then begin
ajk = reform(a(j,0:j-1)*d(0:j-1), /overwrite)
wh = where(ajk NE 0, nk)
if nk GT 0 then $
a(j+1:*,j) = a(j+1:*,j) - a(j+1:*,wh) # ajk(wh)
endif else begin
a(j+1:*,j) = a(j+1:*,j) - $
a(j+1:*,0:j-1) # reform(a(j,0:j-1)*d(0:j-1), /overwrite)
endelse
endif
;; Compute correction to diagonal element
thj2 = max(a(j:*,j))^2
;; Compute the unusual modified factorization of Xie and
;; Schlick, or else default to the standard Gill, Murray & Wright
if n_elements(tau) GT 0 then begin
ww = a(j,j) + tau(0)
if ww GT del then d(j) = max([ww, thj2/bound]) $
else if abs(ww) LE del then d(j) = del $
else d(j) = min([ww,-thj2/bound])
endif else begin
d(j) = max([del, abs(a(j,j)), thj2/bound])
endelse
e(j) = d(j) - a(j,j)
;; Apply corrections
if j LT n-1 then begin
i = lindgen(n-1-j)+j+1
a(i,i) = a(i,i) - a(i,j)^2/d(j)
a(j+1:*,j) = a(j+1:*,j) / d(j)
endif
endfor
;; Invert the permutation vector
ipp = sort(pp)
;; Expand to a full matrix if requested
if keyword_set(cholsol) then begin
d = sqrt(d)
for j = 0, n-2 do a(j+1:*,j) = a(j+1:*,j)*d(j)
a = transpose(temporary(a))
endif else if keyword_set(full) then begin
for j = 0, n-1 do a(0:j,j) = 0
a(diag) = 1
dd = a*0
ee = dd
dd(diag) = d
ee(diag) = e
d = dd
e = ee
endif
end