Skip to content

Commit 76292e4

Browse files
committed
Updated Libraries
1 parent 7a74a90 commit 76292e4

File tree

7 files changed

+511
-0
lines changed

7 files changed

+511
-0
lines changed
23.2 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
2+
<!DOCTYPE html
3+
PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
4+
<html><head>
5+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6+
<!--
7+
This HTML was auto-generated from MATLAB code.
8+
To make changes, update the MATLAB code and republish this document.
9+
--><title>nearestSPD_demo</title><meta name="generator" content="MATLAB 8.1"><link rel="schema.DC" href="http://purl.org/dc/elements/1.1/"><meta name="DC.date" content="2013-07-30"><meta name="DC.source" content="nearestSPD_demo.m"><style type="text/css">
10+
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}:focus{outine:0}ins{text-decoration:none}del{text-decoration:line-through}table{border-collapse:collapse;border-spacing:0}
11+
12+
html { min-height:100%; margin-bottom:1px; }
13+
html body { height:100%; margin:0px; font-family:Arial, Helvetica, sans-serif; font-size:10px; color:#000; line-height:140%; background:#fff none; overflow-y:scroll; }
14+
html body td { vertical-align:top; text-align:left; }
15+
16+
h1 { padding:0px; margin:0px 0px 25px; font-family:Arial, Helvetica, sans-serif; font-size:1.5em; color:#d55000; line-height:100%; font-weight:normal; }
17+
h2 { padding:0px; margin:0px 0px 8px; font-family:Arial, Helvetica, sans-serif; font-size:1.2em; color:#000; font-weight:bold; line-height:140%; border-bottom:1px solid #d6d4d4; display:block; }
18+
h3 { padding:0px; margin:0px 0px 5px; font-family:Arial, Helvetica, sans-serif; font-size:1.1em; color:#000; font-weight:bold; line-height:140%; }
19+
20+
a { color:#005fce; text-decoration:none; }
21+
a:hover { color:#005fce; text-decoration:underline; }
22+
a:visited { color:#004aa0; text-decoration:none; }
23+
24+
p { padding:0px; margin:0px 0px 20px; }
25+
img { padding:0px; margin:0px 0px 20px; border:none; }
26+
p img, pre img, tt img, li img { margin-bottom:0px; }
27+
28+
ul { padding:0px; margin:0px 0px 20px 23px; list-style:square; }
29+
ul li { padding:0px; margin:0px 0px 7px 0px; }
30+
ul li ul { padding:5px 0px 0px; margin:0px 0px 7px 23px; }
31+
ul li ol li { list-style:decimal; }
32+
ol { padding:0px; margin:0px 0px 20px 0px; list-style:decimal; }
33+
ol li { padding:0px; margin:0px 0px 7px 23px; list-style-type:decimal; }
34+
ol li ol { padding:5px 0px 0px; margin:0px 0px 7px 0px; }
35+
ol li ol li { list-style-type:lower-alpha; }
36+
ol li ul { padding-top:7px; }
37+
ol li ul li { list-style:square; }
38+
39+
.content { font-size:1.2em; line-height:140%; padding: 20px; }
40+
41+
pre, tt, code { font-size:12px; }
42+
pre { margin:0px 0px 20px; }
43+
pre.error { color:red; }
44+
pre.codeinput { padding:10px; border:1px solid #d3d3d3; background:#f7f7f7; }
45+
pre.codeoutput { padding:10px 11px; margin:0px 0px 20px; color:#4c4c4c; }
46+
47+
@media print { pre.codeinput, pre.codeoutput { word-wrap:break-word; width:100%; } }
48+
49+
span.keyword { color:#0000FF }
50+
span.comment { color:#228B22 }
51+
span.string { color:#A020F0 }
52+
span.untermstring { color:#B20000 }
53+
span.syscmd { color:#B28C00 }
54+
55+
.footer { width:auto; padding:10px 0px; margin:25px 0px 0px; border-top:1px dotted #878787; font-size:0.8em; line-height:140%; font-style:italic; color:#878787; text-align:left; float:none; }
56+
.footer p { margin:0px; }
57+
.footer a { color:#878787; }
58+
.footer a:hover { color:#878787; text-decoration:underline; }
59+
.footer a:visited { color:#878787; }
60+
61+
table th { padding:7px 5px; text-align:left; vertical-align:middle; border: 1px solid #d6d4d4; font-weight:bold; }
62+
table td { padding:7px 5px; text-align:left; vertical-align:top; border:1px solid #d6d4d4; }
63+
64+
65+
66+
67+
68+
</style></head><body><div class="content"><h2>Contents</h2><div><ul><li><a href="#2">nearestSPD works on any matrix, and it is reasonably fast.</a></li><li><a href="#7">A realistic test case</a></li></ul></div><pre class="codeinput"><span class="comment">% Neearest Symmetric, Positive Definite matrices</span>
69+
<span class="comment">%</span>
70+
<span class="comment">% This tool saves your covariance matrices, turning them into something</span>
71+
<span class="comment">% that really does have the property you will need. That is, when you are</span>
72+
<span class="comment">% trying to use a covariance matrix in a tool like mvnrnd, it makes no</span>
73+
<span class="comment">% sense if your matrix is not positive definite. So mvnrnd will fail in</span>
74+
<span class="comment">% that case.</span>
75+
<span class="comment">%</span>
76+
<span class="comment">% But sometimes, it appears that users end up with matrices that are NOT</span>
77+
<span class="comment">% symmetric and positive definite (commonly abbreviated as SPD) and they</span>
78+
<span class="comment">% still wish to use them to generate random numbers, often in a tool like</span>
79+
<span class="comment">% mvnrnd. A solution is to find the NEAREST matrix that has the desired</span>
80+
<span class="comment">% property of being SPD.</span>
81+
<span class="comment">%</span>
82+
<span class="comment">% I see the question come up every once in a while, so I looked in the file</span>
83+
<span class="comment">% exchange to see what is in there. All I found was nearest_posdef. While</span>
84+
<span class="comment">% this usually almost works, it could be better. It actually failed</span>
85+
<span class="comment">% completely on most of my test cases, and it was not as fast as I would</span>
86+
<span class="comment">% like, using an optimization. In fact, in the comments to nearest_posdef,</span>
87+
<span class="comment">% a logical alternative was posed. That alternative too has its failures,</span>
88+
<span class="comment">% so I wrote nearestSPD.</span>
89+
</pre><h2>nearestSPD works on any matrix, and it is reasonably fast.<a name="2"></a></h2><p>As a test, randn generates a matrix that is not symmetric nor is it at all positive definite in general.</p><pre class="codeinput">U = randn(100);
90+
</pre><p>nearestSPD will be able to convert U into something that is indeed SPD, and for a 100 by 100 matrix, do it quickly enough</p><pre class="codeinput">tic,Uj = nearestSPD(U);toc
91+
</pre><pre class="codeoutput">Elapsed time is 0.005662 seconds.
92+
</pre><p>The ultimate test of course, is to use chol. If chol returns a second argument that is zero, then MATLAB (and mvnrnd) will be happy!</p><pre class="codeinput">[R,p] = chol(Uj);
93+
p
94+
</pre><pre class="codeoutput">p =
95+
0
96+
</pre><p>As you can see, mvnrnd did not complain at all.</p><pre class="codeinput">mvnrnd(zeros(1,100),Uj,1)
97+
</pre><pre class="codeoutput">ans =
98+
Columns 1 through 7
99+
0.6206 -1.3824 0.8017 -0.2522 -1.6578 -3.7084 -3.7997
100+
Columns 8 through 14
101+
0.2101 0.3997 2.6344 -0.6991 -1.5288 -0.0655 1.2957
102+
Columns 15 through 21
103+
0.5635 -1.7118 1.4419 -1.9975 0.9702 -0.7824 -0.9111
104+
Columns 22 through 28
105+
-0.6243 -0.9555 -0.8648 3.9613 -2.9741 0.8544 -1.7191
106+
Columns 29 through 35
107+
2.2032 -1.1222 -0.8262 -2.1132 2.0091 -0.8174 -0.2515
108+
Columns 36 through 42
109+
-0.3103 -0.8486 -0.1352 -3.4751 1.5693 0.0114 -2.6613
110+
Columns 43 through 49
111+
-1.5377 -3.7468 1.7067 -1.0868 -0.3011 -1.2440 0.1904
112+
Columns 50 through 56
113+
-0.7234 0.9335 2.2027 0.9502 0.1257 -4.4532 -0.0755
114+
Columns 57 through 63
115+
-1.5667 -0.5757 -0.6353 -0.5836 -2.5437 -0.3126 2.4465
116+
Columns 64 through 70
117+
2.1601 0.4921 1.4246 3.0354 1.4341 1.5398 5.4932
118+
Columns 71 through 77
119+
0.2329 0.7502 0.4124 -1.5297 2.3390 -1.3886 -1.6488
120+
Columns 78 through 84
121+
-0.1357 -1.4652 -3.4085 -0.4108 0.1225 2.0608 2.4883
122+
Columns 85 through 91
123+
3.1590 -1.7421 -1.3297 5.2951 -0.9421 -0.2190 -1.3316
124+
Columns 92 through 98
125+
1.1916 -0.3938 -1.3629 -2.7727 0.6032 0.0956 -0.8630
126+
Columns 99 through 100
127+
-1.7379 -1.5663
128+
</pre><p>nearest_posdef would have failed here, as U was not even symmetric, nor does it even have positive diagonal entries.</p><h2>A realistic test case<a name="7"></a></h2><p>Next I'll try a simpler test case. This one will have positive diamgonal entries, and it will indeed be symmetric. So this matrix is much closer to a true covariance matrix than that first mess we tried. And since nearest_posdef was quite slow on a 100x100 matrix, I'll use something smaller.</p><pre class="codeinput">U = rand(25);
129+
U = (U + U')/2;
130+
</pre><p>Really, it is meaningless as a covariance matrix, because it is clearly not positive definite. We can see many negative eigenvalues, and chol gets upset. So mvnrnd would fail here.</p><pre class="codeinput">eig(U)'
131+
[R,p] = chol(U);
132+
p
133+
</pre><pre class="codeoutput">ans =
134+
Columns 1 through 7
135+
-1.6748 -1.3845 -1.3380 -1.2144 -1.0673 -0.9856 -0.6934
136+
Columns 8 through 14
137+
-0.6398 -0.4336 -0.3471 -0.2484 -0.1653 -0.0160 0.1141
138+
Columns 15 through 21
139+
0.4482 0.5044 0.5448 0.6195 0.6827 0.9338 1.3431
140+
Columns 22 through 25
141+
1.4957 1.7989 2.0236 12.1344
142+
p =
143+
3
144+
</pre><p>nearest_posdef took a bit of time, about 9 seconds on my machine. Admittedly, much of that time was wasted in doing fancy graphics that nobody actually needs if they just need a result.</p><pre class="codeinput">tic,Um = nearest_posdef(U);toc
145+
</pre><pre class="codeoutput">Elapsed time is 8.768167 seconds.
146+
</pre><img vspace="5" hspace="5" src="nearestSPD_demo_01.png" alt=""> <p>Is Um truly positive definite according to chol? Sadly, it is usually not.</p><pre class="codeinput">[R,p] = chol(Um);
147+
p
148+
</pre><pre class="codeoutput">p =
149+
18
150+
</pre><p>We can see how it failed, by looking at what eig returns.</p><pre class="codeinput">eig(Um)
151+
</pre><pre class="codeoutput">ans =
152+
11.8426 + 0.0000i
153+
1.7845 + 0.0000i
154+
1.5092 + 0.0000i
155+
1.2512 + 0.0000i
156+
1.0957 + 0.0000i
157+
0.7042 + 0.0000i
158+
0.4589 + 0.0000i
159+
0.3627 + 0.0000i
160+
0.3080 + 0.0000i
161+
0.2593 + 0.0000i
162+
0.1571 + 0.0000i
163+
0.0417 + 0.0000i
164+
0.0387 + 0.0000i
165+
0.0290 + 0.0000i
166+
0.0190 + 0.0000i
167+
0.0052 + 0.0000i
168+
0.0021 + 0.0000i
169+
-0.0000 + 0.0000i
170+
-0.0000 - 0.0000i
171+
-0.0000 + 0.0000i
172+
0.0000 + 0.0000i
173+
0.0000 + 0.0000i
174+
-0.0000 + 0.0000i
175+
-0.0000 - 0.0000i
176+
0.0000 + 0.0000i
177+
</pre><p>There will usually be some tiny negative eigenvalues</p><pre class="codeinput">min(real(eig(Um)))
178+
</pre><pre class="codeoutput">ans =
179+
-1.8002e-16
180+
</pre><p>and sometimes even some imaginary eigenvalues. All usually tiny, but still enough to upset chol.</p><pre class="codeinput">max(imag(eig(Um)))
181+
</pre><pre class="codeoutput">ans =
182+
2.2548e-16
183+
</pre><p>The trick suggested by Shuo Han is pretty fast, but it too fails. Since U is already symmetric, we need not symmetrize it first, but chol still gets upset.</p><p>Note that the slash used by Shuo was not a good idea. transpose would have been sufficient.</p><pre class="codeinput">[V,D] = eig(U);
184+
U_psd = V * max(D,0) / V;
185+
[R,p] = chol(U_psd);
186+
p
187+
</pre><pre class="codeoutput">p =
188+
13
189+
</pre><p>Whereas nearestSPD works nicely.</p><pre class="codeinput">Uj = nearestSPD(U);
190+
[R,p] = chol(Uj);
191+
</pre><p>nearestSPD returns a solution that is a bit closer to the original matrix U too. Thus comparing 1,2,inf and Frobenious norms, nearestSPD was better under all norms in my tests, even though it is designed only to optimize the Frobenious norm.</p><pre class="codeinput">[norm(U - Um,1), norm(U - Um,2), norm(U - Um,inf), norm(U - Um,<span class="string">'fro'</span>)]
192+
[norm(U - Uj,1), norm(U - Uj,2), norm(U - Uj,inf), norm(U - Uj,<span class="string">'fro'</span>)]
193+
</pre><pre class="codeoutput">ans =
194+
3.6183 1.6779 3.6183 3.5082
195+
ans =
196+
3.1950 1.6748 3.1950 3.3743
197+
</pre><p class="footer"><br><a href="http://www.mathworks.com/products/matlab/">Published with MATLAB&reg; R2013a</a><br></p></div><!--
198+
##### SOURCE BEGIN #####
199+
% Neearest Symmetric, Positive Definite matrices
200+
%
201+
% This tool saves your covariance matrices, turning them into something
202+
% that really does have the property you will need. That is, when you are
203+
% trying to use a covariance matrix in a tool like mvnrnd, it makes no
204+
% sense if your matrix is not positive definite. So mvnrnd will fail in
205+
% that case.
206+
%
207+
% But sometimes, it appears that users end up with matrices that are NOT
208+
% symmetric and positive definite (commonly abbreviated as SPD) and they
209+
% still wish to use them to generate random numbers, often in a tool like
210+
% mvnrnd. A solution is to find the NEAREST matrix that has the desired
211+
% property of being SPD.
212+
%
213+
% I see the question come up every once in a while, so I looked in the file
214+
% exchange to see what is in there. All I found was nearest_posdef. While
215+
% this usually almost works, it could be better. It actually failed
216+
% completely on most of my test cases, and it was not as fast as I would
217+
% like, using an optimization. In fact, in the comments to nearest_posdef,
218+
% a logical alternative was posed. That alternative too has its failures,
219+
% so I wrote nearestSPD.
220+
221+
%% nearestSPD works on any matrix, and it is reasonably fast.
222+
% As a test, randn generates a matrix that is not symmetric nor is it at
223+
% all positive definite in general.
224+
U = randn(100);
225+
226+
%%
227+
% nearestSPD will be able to convert U into something that is indeed SPD,
228+
% and for a 100 by 100 matrix, do it quickly enough
229+
tic,Uj = nearestSPD(U);toc
230+
231+
%%
232+
% The ultimate test of course, is to use chol. If chol returns a second
233+
% argument that is zero, then MATLAB (and mvnrnd) will be happy!
234+
[R,p] = chol(Uj);
235+
p
236+
237+
%%
238+
% As you can see, mvnrnd did not complain at all.
239+
mvnrnd(zeros(1,100),Uj,1)
240+
241+
%%
242+
% nearest_posdef would have failed here, as U was not even symmetric, nor
243+
% does it even have positive diagonal entries.
244+
245+
%% A realistic test case
246+
% Next I'll try a simpler test case. This one will have positive diamgonal
247+
% entries, and it will indeed be symmetric. So this matrix is much closer
248+
% to a true covariance matrix than that first mess we tried. And since
249+
% nearest_posdef was quite slow on a 100x100 matrix, I'll use something
250+
% smaller.
251+
U = rand(25);
252+
U = (U + U')/2;
253+
254+
%%
255+
% Really, it is meaningless as a covariance matrix, because it is clearly
256+
% not positive definite. We can see many negative eigenvalues, and chol
257+
% gets upset. So mvnrnd would fail here.
258+
eig(U)'
259+
[R,p] = chol(U);
260+
p
261+
262+
%%
263+
% nearest_posdef took a bit of time, about 9 seconds on my machine.
264+
% Admittedly, much of that time was wasted in doing fancy graphics that
265+
% nobody actually needs if they just need a result.
266+
tic,Um = nearest_posdef(U);toc
267+
268+
%%
269+
% Is Um truly positive definite according to chol? Sadly, it is usually not.
270+
[R,p] = chol(Um);
271+
p
272+
273+
%%
274+
% We can see how it failed, by looking at what eig returns.
275+
eig(Um)
276+
%%
277+
% There will usually be some tiny negative eigenvalues
278+
min(real(eig(Um)))
279+
%%
280+
% and sometimes even some imaginary eigenvalues. All usually tiny, but
281+
% still enough to upset chol.
282+
max(imag(eig(Um)))
283+
284+
%%
285+
% The trick suggested by Shuo Han is pretty fast, but it too fails. Since
286+
% U is already symmetric, we need not symmetrize it first, but chol still
287+
% gets upset.
288+
%
289+
% Note that the slash used by Shuo was not a good idea. transpose would
290+
% have been sufficient.
291+
[V,D] = eig(U);
292+
U_psd = V * max(D,0) / V;
293+
[R,p] = chol(U_psd);
294+
p
295+
296+
%%
297+
% Whereas nearestSPD works nicely.
298+
Uj = nearestSPD(U);
299+
[R,p] = chol(Uj);
300+
301+
%%
302+
% nearestSPD returns a solution that is a bit closer to the original
303+
% matrix U too. Thus comparing 1,2,inf and Frobenious norms, nearestSPD was
304+
% better under all norms in my tests, even though it is designed only to
305+
% optimize the Frobenious norm.
306+
[norm(U - Um,1), norm(U - Um,2), norm(U - Um,inf), norm(U - Um,'fro')]
307+
[norm(U - Uj,1), norm(U - Uj,2), norm(U - Uj,inf), norm(U - Uj,'fro')]
308+
309+
310+
##### SOURCE END #####
311+
--></body></html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
function Ahat = nearestSPD(A)
2+
% nearestSPD - the nearest (in Frobenius norm) Symmetric Positive Definite matrix to A
3+
% usage: Ahat = nearestSPD(A)
4+
%
5+
% From Higham: "The nearest symmetric positive semidefinite matrix in the
6+
% Frobenius norm to an arbitrary real matrix A is shown to be (B + H)/2,
7+
% where H is the symmetric polar factor of B=(A + A')/2."
8+
%
9+
% http://www.sciencedirect.com/science/article/pii/0024379588902236
10+
%
11+
% arguments: (input)
12+
% A - square matrix, which will be converted to the nearest Symmetric
13+
% Positive Definite Matrix.
14+
%
15+
% Arguments: (output)
16+
% Ahat - The matrix chosen as the nearest SPD matrix to A.
17+
18+
if nargin ~= 1
19+
error('Exactly one argument must be provided.')
20+
end
21+
22+
% test for a square matrix A
23+
[r,c] = size(A);
24+
if r ~= c
25+
error('A must be a square matrix.')
26+
elseif (r == 1) && (A <= 0)
27+
% A was scalar and non-positive, so just return eps
28+
Ahat = eps;
29+
return
30+
end
31+
32+
% symmetrize A into B
33+
B = (A + A')/2;
34+
35+
% Compute the symmetric polar factor of B. Call it H.
36+
% Clearly H is itself SPD.
37+
[U,Sigma,V] = svd(B);
38+
H = V*Sigma*V';
39+
40+
% get Ahat in the above formula
41+
Ahat = (B+H)/2;
42+
43+
% ensure symmetry
44+
Ahat = (Ahat + Ahat')/2;
45+
46+
% test that Ahat is in fact PD. if it is not so, then tweak it just a bit.
47+
p = 1;
48+
k = 0;
49+
while p ~= 0
50+
[R,p] = chol(Ahat);
51+
k = k + 1;
52+
if p ~= 0
53+
% Ahat failed the chol test. It must have been just a hair off,
54+
% due to floating point trash, so it is simplest now just to
55+
% tweak by adding a tiny multiple of an identity matrix.
56+
mineig = min(eig(Ahat));
57+
Ahat = Ahat + (-mineig*k.^2 + eps(mineig))*eye(size(A));
58+
end
59+
end
60+
61+
62+
63+
64+
65+

0 commit comments

Comments
 (0)