-
Notifications
You must be signed in to change notification settings - Fork 107
Expand file tree
/
Copy pathcheckmanifold.m
More file actions
236 lines (210 loc) · 9.12 KB
/
checkmanifold.m
File metadata and controls
236 lines (210 loc) · 9.12 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
function checkmanifold(M)
% Run a collection of tests on a manifold obtained from a manopt factory
%
% function checkmanifold(M)
%
% M should be a manifold structure obtained from a Manopt factory. This
% tool runs a collection of tests on M to verify (to some extent) that M is
% indeed a valid description of a Riemannian manifold.
%
% This tool is work in progress: your suggestions for additional tests are
% welcome on our forum or as pull requests on GitHub.
%
% See also: checkretraction
% This file is part of Manopt: www.manopt.org.
% Original author: Nicolas Boumal, Aug. 31, 2018.
% Contributors: Andreea-Alexandra Musat
% Change log:
% April 12, 2020 (NB):
% Now checking M.dist(x, M.exp(x, v, t)) for several values of t
% because this test is only valid for norm(x, tv) <= inj(x).
% May 19, 2020 (NB):
% Now checking M.dim().
% Jan 8, 2021 (NB):
% Added partial checks of M.inner, M.tangent2ambient, M.proj, ...
% Dec 29, 2022 (AAM):
% Added checks for M.isotransp.
assert(isstruct(M), 'M must be a structure.');
%% List required fields that must be function handles here
list_of_functions = {'name', 'dim', 'inner', 'norm', 'typicaldist', ...
'proj', 'tangent', 'egrad2rgrad', 'retr', ...
'rand', 'randvec', 'zerovec', 'lincomb'};
for k = 1 : numel(list_of_functions)
field = list_of_functions{k};
if ~(isfield(M, field) && isa(M.(field), 'function_handle'))
fprintf('M.%s must be a function handle.\n', field);
end
end
%% List recommended fields that should be function handles here
list_of_functions = {'dist', 'ehess2rhess', 'exp', 'log', 'hash', ...
'transp', 'pairmean', 'vec', 'mat', ...
'vecmatareisometries'};
for k = 1 : numel(list_of_functions)
field = list_of_functions{k};
if ~(isfield(M, field) && isa(M.(field), 'function_handle'))
fprintf(['M.%s should ideally (but does not have to) be ' ...
'a function handle.\n'], field);
end
end
%% Check random generators
try
x = M.rand();
v = M.randvec(x);
fprintf('Random tangent vector norm: %g (should be 1).\n', ...
M.norm(x, v));
z = M.lincomb(x, 1, v, -1, v);
fprintf('norm(v - v)_x = %g (should be 0).\n', M.norm(x, z));
catch up %#ok<NASGU>
fprintf('Couldn''t check rand, randvec, lincomb.\n');
end
%% Check inner product
try
x = M.rand();
% Check symmetry
u = M.randvec(x);
v = M.randvec(x);
uv = M.inner(x, u, v);
vu = M.inner(x, v, u);
fprintf('<u, v>_x = %g, <v, u>_x = %g, difference = %g (should be 0).\n', uv, vu, uv-vu);
% Check linearity (and owing to symmetry: bilinearity)
a = randn();
b = randn();
w = M.lincomb(x, a, u, b, v);
z = M.randvec(x);
wz = M.inner(x, w, z);
wzbis = a*M.inner(x, u, z) + b*M.inner(x, v, z);
fprintf('<au+bv, z>_x = %g, a<u, z>_x + b<v, z>_x = %g, difference = %g (should be 0).\n', wz, wzbis, wz-wzbis);
% Should check positive definiteness too: it's somehow part of the
% check for M.dim() below.
catch up %#ok<NASGU>
fprintf('Couldn''t check inner.\n');
end
%% Check tangent2ambient, proj, norm
try
x = M.rand();
v = M.randvec(x);
va = M.tangent2ambient(x, v);
vp = M.proj(x, va);
v_min_vp = M.lincomb(x, 1, v, -1, vp);
df = M.norm(x, v_min_vp);
fprintf('Norm of tangent vector minus its projection to tangent space: %g (should be zero).\n', df);
% Should check that proj is linear, self-adjoint, idempotent.
% The issue for generic code is that manifolds do not provide means
% to generate random vectors in the ambient space.
catch up %#ok<NASGU>
fprintf('Couldn''t check tangent2ambient, proj, norm\n');
end
%% Checking exp and dist
try
x = M.rand();
v = M.randvec(x);
for t = logspace(-8, 1, 10)
y = M.exp(x, v, t);
d = M.dist(x, y);
err = d - abs(t)*M.norm(x, v);
fprintf(['dist(x, M.exp(x, v, t)) - abs(t)*M.norm(x, v) = ' ...
'%g (t = %.1e; should be zero for small enough t).\n'], ...
err, t);
end
catch up %#ok<NASGU>
fprintf('Couldn''t check exp and dist.\n');
% Perhaps we want to rethrow(up) ?
% Alternatively, we could check if exp and dist are available and
% silently pass this test if not, but this way is more informative.
end
%% Checking mat, vec, vecmatareisometries
try
x = M.rand();
u = M.randvec(x);
v = M.randvec(x);
U = M.vec(x, u);
V = M.vec(x, v);
if ~iscolumn(U) || ~iscolumn(V)
fprintf('M.vec should return column vectors: they are not.\n');
end
if ~isreal(U) || ~isreal(V)
fprintf('M.vec should return real vectors: they are not real.\n');
end
fprintf(['Unless otherwise stated, M.vec seems to return real ' ...
'column vectors, as intended.\n']);
ru = M.norm(x, M.lincomb(x, 1, M.mat(x, U), -1, u));
rv = M.norm(x, M.lincomb(x, 1, M.mat(x, V), -1, v));
fprintf(['Checking mat/vec are inverse pairs: ' ...
'%g, %g (should be two zeros).\n'], ru, rv);
a = randn(1);
b = randn(1);
fprintf('Checking if vec is linear: %g (should be zero).\n', ...
norm(M.vec(x, M.lincomb(x, a, u, b, v)) - (a*U + b*V)));
if M.vecmatareisometries()
fprintf('M.vecmatareisometries says true.\n');
else
fprintf('M.vecmatareisometries says false.\n');
end
fprintf('If true, this should be zero: %g.\n', ...
U(:).'*V(:) - M.inner(x, u, v));
catch up %#ok<NASGU>
fprintf('Couldn''t check mat, vec, vecmatareisometries.\n');
end
%% Checking dim
dim_threshold = 200;
if M.dim() <= dim_threshold
x = M.rand();
n = M.dim() + 1;
B = cell(n, 1);
for k = 1 : n
B{k} = M.randvec(x);
end
G = grammatrix(M, x, B);
eigG = sort(real(eig(G)), 'descend');
fprintf('Testing M.dim() (works best when dimension is small):\n');
fprintf('\tIf this number is machine-precision zero, then M.dim() may be too large: %g\n', eigG(n-1));
fprintf('\tIf this number is not machine-precision zero, then M.dim() may be too small: %g\n', eigG(n));
else
fprintf('M.dim() not tested because it is > %d.\n', dim_threshold);
end
%% Checking isotransp
try
x1 = M.rand();
x2 = M.rand();
u = M.randvec(x1);
v = M.randvec(x1);
PT_u_x2 = M.isotransp(x1, x2, u);
PT_v_x2 = M.isotransp(x1, x2, v);
isometry_res = M.inner(x1, u, v) - M.inner(x2, PT_u_x2, PT_v_x2);
reverse_res = M.norm(x1, M.lincomb(x1, ...
1, M.isotransp(x2, x1, PT_u_x2), -1, u));
% TODO: Implement generic way of checking if a vector is tangent.
% Should make this part of the manifold description directly.
% Above, there is proxy code that relies on tangent2ambient + proj.
% fprintf(['Testing isotransp(x1, x2, u) belongs to the tangent ' ...
% 'space of x2: %g (should be zero).\n'], ...
% sum(sum(M.proj(x2, PT_u_x2) - PT_u_x2)));
fprintf(['Currently, no check that isotransp(x1, x2, u) ' ...
'belongs to the tangent space at x2: please check.\n']);
fprintf(['Testing isotransp is an isometry: ' ...
'<u, v> - <isotransp(x1, x2, u), isotransp(x1, x2, v)> = %g' ...
' (should be zero).\n'], isometry_res);
fprintf(['Testing isotransp inverse: norm of ' ...
'isotransp(x2, x1, isotransp(x1, x2, u)) - u = %g ' ...
'(should be zero for parallel transport).\n'], reverse_res);
% Parallel transport u to a point that is halfway along a geodesic,
% then parallel transport to the end point. The result should be
% the same if parallel transporting directly to the end point,
% at least if the geodesic segment is short enough (inj. radius).
dst = 1e-5;
v = M.lincomb(x1, dst, v); % try to keep test local
x_mid = M.exp(x1, v, 0.5);
x_end = M.exp(x1, v, 1);
PT_u_x_mid = M.isotransp(x1, x_mid, u);
PT_PT_u_x_mid_x_end = M.isotransp(x_mid, x_end, PT_u_x_mid);
PT_u_x_end = M.isotransp(x1, x_end, u);
diff = M.lincomb(x_end, 1, PT_PT_u_x_mid_x_end, -1, PT_u_x_end);
diffnrm = M.norm(x_end, diff);
fprintf(['Testing isotransp composition to mid point: %g ' ...
'(should be zero if inj(M, x1) > %.1e).\n'], diffnrm, dst);
catch up %#ok<NASGU>
fprintf('Couldn''t check isotransp.\n');
end
%% Recommend calling checkretraction
fprintf('It is recommended also to call checkretraction.\n');
end