GNU Octave  8.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
gsvd.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 1997-2023 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #ifdef HAVE_CONFIG_H
27 # include <config.h>
28 #endif
29 
30 #include "dMatrix.h"
31 #include "CMatrix.h"
32 #include "dDiagMatrix.h"
33 #include "gsvd.h"
34 
35 #include "defun.h"
36 #include "defun-int.h"
37 #include "error.h"
38 #include "errwarn.h"
39 #include "utils.h"
40 #include "ovl.h"
41 #include "ov.h"
42 
44 
45 template <typename T>
46 static typename math::gsvd<T>::Type
47 gsvd_type (int nargout, int nargin)
48 {
49  if (nargout == 0 || nargout == 1)
50  return octave::math::gsvd<T>::Type::sigma_only;
51  else if (nargin < 3)
52  return octave::math::gsvd<T>::Type::std;
53  else
54  return octave::math::gsvd<T>::Type::economy;
55 }
56 
57 // Named do_gsvd to avoid conflicts with the gsvd class itself.
58 template <typename T>
59 static octave_value_list
60 do_gsvd (const T& A, const T& B,
61  const octave_idx_type nargout, const octave_idx_type nargin,
62  bool is_single = false)
63 {
64  math::gsvd<T> result (A, B, gsvd_type<T> (nargout, nargin));
65 
66  octave_value_list retval (nargout);
67  if (nargout <= 1)
68  {
69  if (is_single)
70  {
71  FloatMatrix sigA = result.singular_values_A ();
72  FloatMatrix sigB = result.singular_values_B ();
73  for (int i = sigA.rows () - 1; i >= 0; i--)
74  sigA.xelem (i) /= sigB.xelem (i);
75  retval(0) = sigA.sort ();
76  }
77  else
78  {
79  Matrix sigA = result.singular_values_A ();
80  Matrix sigB = result.singular_values_B ();
81  for (int i = sigA.rows () - 1; i >= 0; i--)
82  sigA.xelem (i) /= sigB.xelem (i);
83  retval(0) = sigA.sort ();
84  }
85  }
86  else
87  {
88  switch (nargout)
89  {
90  case 5:
91  retval(4) = result.singular_values_B ();
92  OCTAVE_FALLTHROUGH;
93 
94  case 4:
95  retval(3) = result.singular_values_A ();
96  OCTAVE_FALLTHROUGH;
97 
98  case 3:
99  retval(2) = result.right_singular_matrix ();
100  }
101 
102  retval(1) = result.left_singular_matrix_B ();
103  retval(0) = result.left_singular_matrix_A ();
104  }
105 
106  return retval;
107 }
108 
109 DEFUN (gsvd, args, nargout,
110  doc: /* -*- texinfo -*-
111 @deftypefn {} {@var{S} =} gsvd (@var{A}, @var{B})
112 @deftypefnx {} {[@var{U}, @var{V}, @var{X}, @var{C}, @var{S}] =} gsvd (@var{A}, @var{B})
113 @deftypefnx {} {[@var{U}, @var{V}, @var{X}, @var{C}, @var{S}] =} gsvd (@var{A}, @var{B}, 0)
114 Compute the generalized singular value decomposition of (@var{A}, @var{B}).
115 
116 The generalized singular value decomposition is defined by the following
117 relations:
118 
119 @tex
120 $$ A = U C X^\dagger $$
121 $$ B = V S X^\dagger $$
122 $$ C^\dagger C + S^\dagger S = eye (columns (A)) $$
123 @end tex
124 @ifnottex
125 
126 @example
127 @group
128 A = U*C*X'
129 B = V*S*X'
130 C'*C + S'*S = eye (columns (A))
131 @end group
132 @end example
133 
134 @end ifnottex
135 
136 The function @code{gsvd} normally returns just the vector of generalized
137 singular values
138 @tex
139 $$ \sqrt{{{diag (C^\dagger C)} \over {diag (S^\dagger S)}}} $$
140 @end tex
141 @ifnottex
142 @code{sqrt (diag (C'*C) ./ diag (S'*S))}.
143 @end ifnottex
144 If asked for five return values, it also computes
145 @tex
146 $U$, $V$, $X$, and $C$.
147 @end tex
148 @ifnottex
149 U, V, X, and C.
150 @end ifnottex
151 
152 If the optional third input is present, @code{gsvd} constructs the
153 "economy-sized" decomposition where the number of columns of @var{U}, @var{V}
154 and the number of rows of @var{C}, @var{S} is less than or equal to the number
155 of columns of @var{A}. This option is not yet implemented.
156 
157 Programming Note: the code is a wrapper to the corresponding @sc{lapack} dggsvd
158 and zggsvd routines. If matrices @var{A} and @var{B} are @emph{both} rank
159 deficient then @sc{lapack} will return an incorrect factorization. Programmers
160 should avoid this combination.
161 @seealso{svd}
162 @end deftypefn */)
163 {
164  int nargin = args.length ();
165 
166  if (nargin < 2 || nargin > 3)
167  print_usage ();
168  else if (nargin == 3)
169  {
170  // FIXME: when "economy" is implemented delete this code
171  warning ("gsvd: economy-sized decomposition is not yet implemented, returning full decomposition");
172  nargin = 2;
173  }
174 
175  octave_value_list retval;
176 
177  octave_value argA = args(0);
178  octave_value argB = args(1);
179 
180  if (argA.columns () != argB.columns ())
181  error ("gsvd: A and B must have the same number of columns");
182 
183  if (argA.is_single_type () || argB.is_single_type ())
184  {
185  if (argA.isreal () && argB.isreal ())
186  {
187  FloatMatrix tmpA = argA.xfloat_matrix_value ("gsvd: A must be a real or complex matrix");
188  FloatMatrix tmpB = argB.xfloat_matrix_value ("gsvd: B must be a real or complex matrix");
189 
190  if (tmpA.any_element_is_inf_or_nan ())
191  error ("gsvd: A cannot have Inf or NaN values");
192  if (tmpB.any_element_is_inf_or_nan ())
193  error ("gsvd: B cannot have Inf or NaN values");
194 
195  retval = do_gsvd (tmpA, tmpB, nargout, nargin, true);
196  }
197  else if (argA.iscomplex () || argB.iscomplex ())
198  {
199  FloatComplexMatrix ctmpA =
200  argA.xfloat_complex_matrix_value ("gsvd: A must be a real or complex matrix");
201  FloatComplexMatrix ctmpB =
202  argB.xfloat_complex_matrix_value ("gsvd: B must be a real or complex matrix");
203 
204  if (ctmpA.any_element_is_inf_or_nan ())
205  error ("gsvd: A cannot have Inf or NaN values");
206  if (ctmpB.any_element_is_inf_or_nan ())
207  error ("gsvd: B cannot have Inf or NaN values");
208 
209  retval = do_gsvd (ctmpA, ctmpB, nargout, nargin, true);
210  }
211  else
212  error ("gsvd: A and B must be real or complex matrices");
213  }
214  else
215  {
216  if (argA.isreal () && argB.isreal ())
217  {
218  Matrix tmpA = argA.xmatrix_value ("gsvd: A must be a real or complex matrix");
219  Matrix tmpB = argB.xmatrix_value ("gsvd: B must be a real or complex matrix");
220 
221  if (tmpA.any_element_is_inf_or_nan ())
222  error ("gsvd: A cannot have Inf or NaN values");
223  if (tmpB.any_element_is_inf_or_nan ())
224  error ("gsvd: B cannot have Inf or NaN values");
225 
226  retval = do_gsvd (tmpA, tmpB, nargout, nargin);
227  }
228  else if (argA.iscomplex () || argB.iscomplex ())
229  {
230  ComplexMatrix ctmpA = argA.xcomplex_matrix_value ("gsvd: A must be a real or complex matrix");
231  ComplexMatrix ctmpB = argB.xcomplex_matrix_value ("gsvd: B must be a real or complex matrix");
232 
233  if (ctmpA.any_element_is_inf_or_nan ())
234  error ("gsvd: A cannot have Inf or NaN values");
235  if (ctmpB.any_element_is_inf_or_nan ())
236  error ("gsvd: B cannot have Inf or NaN values");
237 
238  retval = do_gsvd (ctmpA, ctmpB, nargout, nargin);
239  }
240  else
241  error ("gsvd: A and B must be real or complex matrices");
242  }
243 
244  return retval;
245 }
246 
247 /*
248 
249 ## Basic tests of decomposition
250 %!test <60273>
251 %! A = reshape (1:15,5,3);
252 %! B = magic (3);
253 %! [U,V,X,C,S] = gsvd (A,B);
254 %! assert (size (U), [5, 5]);
255 %! assert (size (V), [3, 3]);
256 %! assert (size (X), [3, 3]);
257 %! assert (size (C), [5, 3]);
258 %! assert (C(4:5, :), zeros (2,3));
259 %! assert (size (S), [3, 3]);
260 %! assert (U*C*X', A, 50*eps);
261 %! assert (V*S*X', B, 50*eps);
262 %! S0 = gsvd (A, B);
263 %! assert (size (S0), [3, 1]);
264 %! S1 = sort (svd (A / B));
265 %! assert (S0, S1, 10*eps);
266 
267 %!test <60273>
268 %! A = reshape (1:15,3,5);
269 %! B = magic (5);
270 %! [U,V,X,C,S] = gsvd (A,B);
271 %! assert (size (U), [3, 3]);
272 %! assert (size (V), [5, 5]);
273 %! assert (size (X), [5, 5]);
274 %! assert (size (C), [3, 5]);
275 %! assert (C(:, 4:5), zeros (3,2));
276 %! assert (size (S), [5, 5]);
277 %! assert (U*C*X', A, 120*eps); # less accurate in this orientation
278 %! assert (V*S*X', B, 150*eps); # for some reason.
279 %! S0 = gsvd (A, B);
280 %! assert (size (S0), [5, 1]);
281 %! S0 = S0(3:end);
282 %! S1 = sort (svd (A / B));
283 %! assert (S0, S1, 20*eps);
284 
285 ## a few tests for gsvd.m
286 %!shared A, A0, B, B0, U, V, C, S, X, old_state, restore_state
287 %! old_state = randn ("state");
288 %! restore_state = onCleanup (@() randn ("state", old_state));
289 %! randn ("state", 40); # initialize generator to make behavior reproducible
290 %! A0 = randn (5, 3);
291 %! B0 = diag ([1 2 4]);
292 %! A = A0;
293 %! B = B0;
294 
295 ## A (5x3) and B (3x3) are full rank
296 %!test <48807>
297 %! [U, V, X, C, S] = gsvd (A, B);
298 %! assert (C'*C + S'*S, eye (3), 5*eps);
299 %! assert (U*C*X', A, 10*eps);
300 %! assert (V*S*X', B, 20*eps);
301 
302 ## A: 5x3 full rank, B: 3x3 rank deficient
303 %!test <48807>
304 %! B(2, 2) = 0;
305 %! [U, V, X, C, S] = gsvd (A, B);
306 %! assert (C'*C + S'*S, eye (3), 5*eps);
307 %! assert (U*C*X', A, 10*eps);
308 %! assert (V*S*X', B, 20*eps);
309 
310 ## A: 5x3 rank deficient, B: 3x3 full rank
311 %!test <48807>
312 %! B = B0;
313 %! A(:, 3) = 2*A(:, 1) - A(:, 2);
314 %! [U, V, X, C, S] = gsvd (A, B);
315 %! assert (C'*C + S'*S, eye (3), 5*eps);
316 %! assert (U*C*X', A, 10*eps);
317 %! assert (V*S*X', B, 20*eps);
318 
319 ## A and B are both rank deficient
320 ## FIXME: LAPACK seems to be completely broken for this case
321 %!#test <48807>
322 %! B(:, 3) = 2*B(:, 1) - B(:, 2);
323 %! [U, V, X, C, S] = gsvd (A, B);
324 %! assert (C'*C + S'*S, eye (3), 5*eps);
325 %! assert (U*C*X', A, 10*eps);
326 %! assert (V*S*X', B, 20*eps);
327 
328 ## A (now 3x5) and B (now 5x5) are full rank
329 %!test <48807>
330 %! A = A0.';
331 %! B0 = diag ([1 2 4 8 16]);
332 %! B = B0;
333 %! [U, V, X, C, S] = gsvd (A, B);
334 %! assert (C'*C + S'*S, eye (5), 5*eps);
335 %! assert (U*C*X', A, 15*eps);
336 %! assert (V*S*X', B, 85*eps);
337 
338 ## A: 3x5 full rank, B: 5x5 rank deficient
339 %!test <48807>
340 %! B(2, 2) = 0;
341 %! [U, V, X, C, S] = gsvd (A, B);
342 %! assert (C'*C + S'*S, eye (5), 5*eps);
343 %! assert (U*C*X', A, 15*eps);
344 %! assert (V*S*X', B, 85*eps);
345 
346 ## A: 3x5 rank deficient, B: 5x5 full rank
347 %!test <48807>
348 %! B = B0;
349 %! A(3, :) = 2*A(1, :) - A(2, :);
350 %! [U, V, X, C, S] = gsvd (A, B);
351 %! assert (C'*C + S'*S, eye (5), 5*eps);
352 %! assert (U*C*X', A, 15*eps);
353 %! assert (V*S*X', B, 85*eps);
354 
355 ## A and B are both rank deficient
356 ## FIXME: LAPACK seems to be completely broken for this case
357 %!#test <48807>
358 %! A = A0.'; B = B0.';
359 %! A(:, 3) = 2*A(:, 1) - A(:, 2);
360 %! B(:, 3) = 2*B(:, 1) - B(:, 2);
361 %! [U, V, X, C, S] = gsvd (A, B);
362 %! assert (C'*C + S'*S, eye (3), 5*eps);
363 %! assert (U*C*X', A, 10*eps);
364 %! assert (V*S*X', B, 20*eps);
365 
366 ## A: 5x3 complex full rank, B: 3x3 complex full rank
367 %!test <48807>
368 %! A0 = A0 + j* randn (5, 3);
369 %! B0 = diag ([1 2 4]) + j* diag ([4 -2 -1]);
370 %! A = A0;
371 %! B = B0;
372 %! [U, V, X, C, S] = gsvd (A, B);
373 %! assert (C'*C + S'*S, eye (3), 5*eps);
374 %! assert (U*C*X', A, 10*eps);
375 %! assert (V*S*X', B, 25*eps);
376 
377 ## A: 5x3 complex full rank, B: 3x3 complex rank deficient
378 %!test <48807>
379 %! B(2, 2) = 0;
380 %! [U, V, X, C, S] = gsvd (A, B);
381 %! assert (C'*C + S'*S, eye (3), 5*eps);
382 %! assert (U*C*X', A, 10*eps);
383 %! assert (V*S*X', B, 25*eps);
384 
385 ## A: 5x3 complex rank deficient, B: 3x3 complex full rank
386 %!test <48807>
387 %! B = B0;
388 %! A(:, 3) = 2*A(:, 1) - A(:, 2);
389 %! [U, V, X, C, S] = gsvd (A, B);
390 %! assert (C'*C + S'*S, eye (3), 5*eps);
391 %! assert (U*C*X', A, 15*eps);
392 %! assert (V*S*X', B, 25*eps);
393 
394 ## A (5x3) and B (3x3) are both complex rank deficient
395 ## FIXME: LAPACK seems to be completely broken for this case
396 %!#test <48807>
397 %! B(:, 3) = 2*B(:, 1) - B(:, 2);
398 %! [U, V, X, C, S] = gsvd (A, B);
399 %! assert (C'*C + S'*S, eye (3), 5*eps);
400 %! assert (U*C*X', A, 10*eps);
401 %! assert (V*S*X', B, 20*eps);
402 
403 ## A (now 3x5) complex and B (now 5x5) complex are full rank
404 ## now, A is 3x5
405 %!test <48807>
406 %! A = A0.';
407 %! B0 = diag ([1 2 4 8 16]) + j* diag ([-5 4 -3 2 -1]);
408 %! B = B0;
409 %! [U, V, X, C, S] = gsvd (A, B);
410 %! assert (C'*C + S'*S, eye (5), 5*eps);
411 %! assert (U*C*X', A, 25*eps);
412 %! assert (V*S*X', B, 85*eps);
413 
414 ## A: 3x5 complex full rank, B: 5x5 complex rank deficient
415 %!test <48807>
416 %! B(2, 2) = 0;
417 %! [U, V, X, C, S] = gsvd (A, B);
418 %! assert (C'*C + S'*S, eye (5), 5*eps);
419 %! assert (U*C*X', A, 10*eps);
420 %! assert (V*S*X', B, 85*eps);
421 
422 ## A: 3x5 complex rank deficient, B: 5x5 complex full rank
423 %!test <48807>
424 %! B = B0;
425 %! A(3, :) = 2*A(1, :) - A(2, :);
426 %! [U, V, X, C, S] = gsvd (A, B);
427 %! assert (C'*C + S'*S, eye (5), 5*eps);
428 %! assert (U*C*X', A, 10*eps);
429 %! assert (V*S*X', B, 85*eps);
430 
431 ## A and B are both complex rank deficient
432 ## FIXME: LAPACK seems to be completely broken for this case
433 %!#test <48807>
434 %! A = A0.';
435 %! B = B0.';
436 %! A(:, 3) = 2*A(:, 1) - A(:, 2);
437 %! B(:, 3) = 2*B(:, 1) - B(:, 2);
438 %! [U, V, X, C, S] = gsvd (A, B);
439 %! assert (C'*C + S'*S, eye (5), 5*eps);
440 %! assert (U*C*X', A, 10*eps);
441 %! assert (V*S*X', B, 85*eps);
442 
443 ## Test that single inputs produce single outputs
444 %!test
445 %! s = gsvd (single (eye (5)), B);
446 %! assert (class (s), "single");
447 %! [U,V,X,C,S] = gsvd (single (eye(5)), B);
448 %! assert (class (U), "single");
449 %! assert (class (V), "single");
450 %! assert (class (X), "single");
451 %! assert (class (C), "single");
452 %! assert (class (S), "single");
453 %!
454 %! s = gsvd (A, single (eye (5)));
455 %! assert (class (s), "single");
456 %! [U,V,X,C,S] = gsvd (A, single (eye (5)));
457 %! assert (class (U), "single");
458 %! assert (class (V), "single");
459 %! assert (class (X), "single");
460 %! assert (class (C), "single");
461 %! assert (class (S), "single");
462 
463 ## Test input validation
464 %!error <Invalid call> gsvd ()
465 %!error <Invalid call> gsvd (1)
466 %!error <Invalid call> gsvd (1,2,3,4)
467 %!warning <economy-sized decomposition is not yet implemented> gsvd (1,2,0);
468 %!error <A and B must have the same number of columns> gsvd (1,[1, 2])
469 ## Test input validation for single (real and complex) inputs.
470 %!error <A cannot have Inf or NaN values> gsvd (Inf, single (2))
471 %!error <A cannot have Inf or NaN values> gsvd (NaN, single (2))
472 %!error <B cannot have Inf or NaN values> gsvd (single (1), Inf)
473 %!error <B cannot have Inf or NaN values> gsvd (single (1), NaN)
474 %!error <A must be a real or complex matrix> gsvd ({1}, single (2i))
475 %!error <B must be a real or complex matrix> gsvd (single (i), {2})
476 %!error <A cannot have Inf or NaN values> gsvd (Inf, single (2i))
477 %!error <A cannot have Inf or NaN values> gsvd (NaN, single (2i))
478 %!error <B cannot have Inf or NaN values> gsvd (single (i), Inf)
479 %!error <B cannot have Inf or NaN values> gsvd (single (i), NaN)
480 ## Test input validation for single, but not real or complex, inputs.
481 %!error <A and B must be real or complex matrices> gsvd ({1}, single (2))
482 %!error <A and B must be real or complex matrices> gsvd (single (1), {2})
483 ## Test input validation for double (real and complex) inputs.
484 %!error <A cannot have Inf or NaN values> gsvd (Inf, 2)
485 %!error <A cannot have Inf or NaN values> gsvd (NaN, 2)
486 %!error <B cannot have Inf or NaN values> gsvd (1, Inf)
487 %!error <B cannot have Inf or NaN values> gsvd (1, NaN)
488 %!error <A must be a real or complex matrix> gsvd ({1}, 2i)
489 %!error <B must be a real or complex matrix> gsvd (i, {2})
490 %!error <A cannot have Inf or NaN values> gsvd (Inf, 2i)
491 %!error <A cannot have Inf or NaN values> gsvd (NaN, 2i)
492 %!error <B cannot have Inf or NaN values> gsvd (i, Inf)
493 %!error <B cannot have Inf or NaN values> gsvd (i, NaN)
494 ## Test input validation for double, but not real or complex, inputs.
495 %!error <A and B must be real or complex matrices> gsvd ({1}, double (2))
496 %!error <A and B must be real or complex matrices> gsvd (double (1), {2})
497 ## Test input validation in liboctave/numeric/gsvd.cc
498 %!error <A and B cannot be empty matrices> gsvd (zeros (0,1), 1)
499 %!error <A and B cannot be empty matrices> gsvd (1, zeros (0,1))
500 
501 */
502 
OCTAVE_END_NAMESPACE(octave)
OCTARRAY_OVERRIDABLE_FUNC_API octave_idx_type rows(void) const
Definition: Array.h:459
OCTARRAY_API Array< T, Alloc > sort(int dim=0, sortmode mode=ASCENDING) const
Size of the specified dimension.
Definition: Array-base.cc:1783
OCTARRAY_OVERRIDABLE_FUNC_API T & xelem(octave_idx_type n)
Size of the specified dimension.
Definition: Array.h:524
OCTAVE_API bool any_element_is_inf_or_nan(void) const
Definition: CNDArray.cc:271
OCTAVE_API bool any_element_is_inf_or_nan(void) const
Definition: fCNDArray.cc:271
OCTAVE_API bool any_element_is_inf_or_nan(void) const
Definition: fNDArray.cc:281
Definition: dMatrix.h:42
OCTAVE_API bool any_element_is_inf_or_nan(void) const
Definition: dNDArray.cc:324
Definition: gsvd.h:39
bool isreal(void) const
Definition: ov.h:783
OCTINTERP_API Matrix xmatrix_value(const char *fmt,...) const
OCTINTERP_API FloatMatrix xfloat_matrix_value(const char *fmt,...) const
octave_idx_type columns(void) const
Definition: ov.h:592
OCTINTERP_API FloatComplexMatrix xfloat_complex_matrix_value(const char *fmt,...) const
OCTINTERP_API ComplexMatrix xcomplex_matrix_value(const char *fmt,...) const
bool is_single_type(void) const
Definition: ov.h:743
bool iscomplex(void) const
Definition: ov.h:786
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
OCTINTERP_API void print_usage(void)
Definition: defun-int.h:72
#define DEFUN(name, args_name, nargout_name, doc)
Macro to define a builtin function.
Definition: defun.h:56
void warning(const char *fmt,...)
Definition: error.cc:1054
void error(const char *fmt,...)
Definition: error.cc:979
static math::gsvd< T >::Type gsvd_type(int nargout, int nargin)
Definition: gsvd.cc:47
static octave_value_list do_gsvd(const T &A, const T &B, const octave_idx_type nargout, const octave_idx_type nargin, bool is_single=false)
Definition: gsvd.cc:60
F77_RET_T const F77_INT F77_CMPLX const F77_INT F77_CMPLX * B
F77_RET_T const F77_INT F77_CMPLX * A