GNU Octave 7.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-2022 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
43OCTAVE_NAMESPACE_BEGIN
44
45template <typename T>
46static typename math::gsvd<T>::Type
47gsvd_type (int nargout, int nargin)
48{
49 if (nargout == 0 || nargout == 1)
51 else if (nargin < 3)
53 else
55}
56
57// Named do_gsvd to avoid conflicts with the gsvd class itself.
58template <typename T>
60do_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
109DEFUN (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)
114Compute the generalized singular value decomposition of (@var{A}, @var{B}).
115
116The generalized singular value decomposition is defined by the following
117relations:
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
128A = U*C*X'
129B = V*S*X'
130C'*C + S'*S = eye (columns (A))
131@end group
132@end example
133
134@end ifnottex
135
136The function @code{gsvd} normally returns just the vector of generalized
137singular 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
144If asked for five return values, it also computes
145@tex
146$U$, $V$, $X$, and $C$.
147@end tex
148@ifnottex
149U, V, X, and C.
150@end ifnottex
151
152If the optional third input is present, @code{gsvd} constructs the
153"economy-sized" decomposition where the number of columns of @var{U}, @var{V}
154and the number of rows of @var{C}, @var{S} is less than or equal to the number
155of columns of @var{A}. This option is not yet implemented.
156
157Programming Note: the code is a wrapper to the corresponding @sc{lapack} dggsvd
158and zggsvd routines. If matrices @var{A} and @var{B} are @emph{both} rank
159deficient then @sc{lapack} will return an incorrect factorization. Programmers
160should 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 = argA.xfloat_complex_matrix_value ("gsvd: A must be a real or complex matrix");
200 FloatComplexMatrix ctmpB = argB.xfloat_complex_matrix_value ("gsvd: B must be a real or complex matrix");
201
202 if (ctmpA.any_element_is_inf_or_nan ())
203 error ("gsvd: A cannot have Inf or NaN values");
204 if (ctmpB.any_element_is_inf_or_nan ())
205 error ("gsvd: B cannot have Inf or NaN values");
206
207 retval = do_gsvd (ctmpA, ctmpB, nargout, nargin, true);
208 }
209 else
210 error ("gsvd: A and B must be real or complex matrices");
211 }
212 else
213 {
214 if (argA.isreal () && argB.isreal ())
215 {
216 Matrix tmpA = argA.xmatrix_value ("gsvd: A must be a real or complex matrix");
217 Matrix tmpB = argB.xmatrix_value ("gsvd: B must be a real or complex matrix");
218
219 if (tmpA.any_element_is_inf_or_nan ())
220 error ("gsvd: A cannot have Inf or NaN values");
221 if (tmpB.any_element_is_inf_or_nan ())
222 error ("gsvd: B cannot have Inf or NaN values");
223
224 retval = do_gsvd (tmpA, tmpB, nargout, nargin);
225 }
226 else if (argA.iscomplex () || argB.iscomplex ())
227 {
228 ComplexMatrix ctmpA = argA.xcomplex_matrix_value ("gsvd: A must be a real or complex matrix");
229 ComplexMatrix ctmpB = argB.xcomplex_matrix_value ("gsvd: B must be a real or complex matrix");
230
231 if (ctmpA.any_element_is_inf_or_nan ())
232 error ("gsvd: A cannot have Inf or NaN values");
233 if (ctmpB.any_element_is_inf_or_nan ())
234 error ("gsvd: B cannot have Inf or NaN values");
235
236 retval = do_gsvd (ctmpA, ctmpB, nargout, nargin);
237 }
238 else
239 error ("gsvd: A and B must be real or complex matrices");
240 }
241
242 return retval;
243}
244
245/*
246
247## Basic tests of decomposition
248%!test <60273>
249%! A = reshape (1:15,5,3);
250%! B = magic (3);
251%! [U,V,X,C,S] = gsvd (A,B);
252%! assert (size (U), [5, 5]);
253%! assert (size (V), [3, 3]);
254%! assert (size (X), [3, 3]);
255%! assert (size (C), [5, 3]);
256%! assert (C(4:5, :), zeros (2,3));
257%! assert (size (S), [3, 3]);
258%! assert (U*C*X', A, 50*eps);
259%! assert (V*S*X', B, 50*eps);
260%! S0 = gsvd (A, B);
261%! assert (size (S0), [3, 1]);
262%! S1 = sort (svd (A / B));
263%! assert (S0, S1, 10*eps);
264
265%!test <60273>
266%! A = reshape (1:15,3,5);
267%! B = magic (5);
268%! [U,V,X,C,S] = gsvd (A,B);
269%! assert (size (U), [3, 3]);
270%! assert (size (V), [5, 5]);
271%! assert (size (X), [5, 5]);
272%! assert (size (C), [3, 5]);
273%! assert (C(:, 4:5), zeros (3,2));
274%! assert (size (S), [5, 5]);
275%! assert (U*C*X', A, 120*eps); # less accurate in this orientation
276%! assert (V*S*X', B, 150*eps); # for some reason.
277%! S0 = gsvd (A, B);
278%! assert (size (S0), [5, 1]);
279%! S0 = S0(3:end);
280%! S1 = sort (svd (A / B));
281%! assert (S0, S1, 20*eps);
282
283## a few tests for gsvd.m
284%!shared A, A0, B, B0, U, V, C, S, X, old_state, restore_state
285%! old_state = randn ("state");
286%! restore_state = onCleanup (@() randn ("state", old_state));
287%! randn ("state", 40); # initialize generator to make behavior reproducible
288%! A0 = randn (5, 3);
289%! B0 = diag ([1 2 4]);
290%! A = A0;
291%! B = B0;
292
293## A (5x3) and B (3x3) are full rank
294%!test <48807>
295%! [U, V, X, C, S] = gsvd (A, B);
296%! assert (C'*C + S'*S, eye (3), 5*eps);
297%! assert (U*C*X', A, 10*eps);
298%! assert (V*S*X', B, 20*eps);
299
300## A: 5x3 full rank, B: 3x3 rank deficient
301%!test <48807>
302%! B(2, 2) = 0;
303%! [U, V, X, C, S] = gsvd (A, B);
304%! assert (C'*C + S'*S, eye (3), 5*eps);
305%! assert (U*C*X', A, 10*eps);
306%! assert (V*S*X', B, 20*eps);
307
308## A: 5x3 rank deficient, B: 3x3 full rank
309%!test <48807>
310%! B = B0;
311%! A(:, 3) = 2*A(:, 1) - A(:, 2);
312%! [U, V, X, C, S] = gsvd (A, B);
313%! assert (C'*C + S'*S, eye (3), 5*eps);
314%! assert (U*C*X', A, 10*eps);
315%! assert (V*S*X', B, 20*eps);
316
317## A and B are both rank deficient
318## FIXME: LAPACK seems to be completely broken for this case
319%!#test <48807>
320%! B(:, 3) = 2*B(:, 1) - B(:, 2);
321%! [U, V, X, C, S] = gsvd (A, B);
322%! assert (C'*C + S'*S, eye (3), 5*eps);
323%! assert (U*C*X', A, 10*eps);
324%! assert (V*S*X', B, 20*eps);
325
326## A (now 3x5) and B (now 5x5) are full rank
327%!test <48807>
328%! A = A0.';
329%! B0 = diag ([1 2 4 8 16]);
330%! B = B0;
331%! [U, V, X, C, S] = gsvd (A, B);
332%! assert (C'*C + S'*S, eye (5), 5*eps);
333%! assert (U*C*X', A, 15*eps);
334%! assert (V*S*X', B, 85*eps);
335
336## A: 3x5 full rank, B: 5x5 rank deficient
337%!test <48807>
338%! B(2, 2) = 0;
339%! [U, V, X, C, S] = gsvd (A, B);
340%! assert (C'*C + S'*S, eye (5), 5*eps);
341%! assert (U*C*X', A, 15*eps);
342%! assert (V*S*X', B, 85*eps);
343
344## A: 3x5 rank deficient, B: 5x5 full rank
345%!test <48807>
346%! B = B0;
347%! A(3, :) = 2*A(1, :) - A(2, :);
348%! [U, V, X, C, S] = gsvd (A, B);
349%! assert (C'*C + S'*S, eye (5), 5*eps);
350%! assert (U*C*X', A, 15*eps);
351%! assert (V*S*X', B, 85*eps);
352
353## A and B are both rank deficient
354## FIXME: LAPACK seems to be completely broken for this case
355%!#test <48807>
356%! A = A0.'; B = B0.';
357%! A(:, 3) = 2*A(:, 1) - A(:, 2);
358%! B(:, 3) = 2*B(:, 1) - B(:, 2);
359%! [U, V, X, C, S] = gsvd (A, B);
360%! assert (C'*C + S'*S, eye (3), 5*eps);
361%! assert (U*C*X', A, 10*eps);
362%! assert (V*S*X', B, 20*eps);
363
364## A: 5x3 complex full rank, B: 3x3 complex full rank
365%!test <48807>
366%! A0 = A0 + j*randn (5, 3);
367%! B0 = diag ([1 2 4]) + j*diag ([4 -2 -1]);
368%! A = A0;
369%! B = B0;
370%! [U, V, X, C, S] = gsvd (A, B);
371%! assert (C'*C + S'*S, eye (3), 5*eps);
372%! assert (U*C*X', A, 10*eps);
373%! assert (V*S*X', B, 25*eps);
374
375## A: 5x3 complex full rank, B: 3x3 complex rank deficient
376%!test <48807>
377%! B(2, 2) = 0;
378%! [U, V, X, C, S] = gsvd (A, B);
379%! assert (C'*C + S'*S, eye (3), 5*eps);
380%! assert (U*C*X', A, 10*eps);
381%! assert (V*S*X', B, 25*eps);
382
383## A: 5x3 complex rank deficient, B: 3x3 complex full rank
384%!test <48807>
385%! B = B0;
386%! A(:, 3) = 2*A(:, 1) - A(:, 2);
387%! [U, V, X, C, S] = gsvd (A, B);
388%! assert (C'*C + S'*S, eye (3), 5*eps);
389%! assert (U*C*X', A, 15*eps);
390%! assert (V*S*X', B, 25*eps);
391
392## A (5x3) and B (3x3) are both complex rank deficient
393## FIXME: LAPACK seems to be completely broken for this case
394%!#test <48807>
395%! B(:, 3) = 2*B(:, 1) - B(:, 2);
396%! [U, V, X, C, S] = gsvd (A, B);
397%! assert (C'*C + S'*S, eye (3), 5*eps);
398%! assert (U*C*X', A, 10*eps);
399%! assert (V*S*X', B, 20*eps);
400
401## A (now 3x5) complex and B (now 5x5) complex are full rank
402## now, A is 3x5
403%!test <48807>
404%! A = A0.';
405%! B0 = diag ([1 2 4 8 16]) + j*diag ([-5 4 -3 2 -1]);
406%! B = B0;
407%! [U, V, X, C, S] = gsvd (A, B);
408%! assert (C'*C + S'*S, eye (5), 5*eps);
409%! assert (U*C*X', A, 25*eps);
410%! assert (V*S*X', B, 85*eps);
411
412## A: 3x5 complex full rank, B: 5x5 complex rank deficient
413%!test <48807>
414%! B(2, 2) = 0;
415%! [U, V, X, C, S] = gsvd (A, B);
416%! assert (C'*C + S'*S, eye (5), 5*eps);
417%! assert (U*C*X', A, 10*eps);
418%! assert (V*S*X', B, 85*eps);
419
420## A: 3x5 complex rank deficient, B: 5x5 complex full rank
421%!test <48807>
422%! B = B0;
423%! A(3, :) = 2*A(1, :) - A(2, :);
424%! [U, V, X, C, S] = gsvd (A, B);
425%! assert (C'*C + S'*S, eye (5), 5*eps);
426%! assert (U*C*X', A, 10*eps);
427%! assert (V*S*X', B, 85*eps);
428
429## A and B are both complex rank deficient
430## FIXME: LAPACK seems to be completely broken for this case
431%!#test <48807>
432%! A = A0.';
433%! B = B0.';
434%! A(:, 3) = 2*A(:, 1) - A(:, 2);
435%! B(:, 3) = 2*B(:, 1) - B(:, 2);
436%! [U, V, X, C, S] = gsvd (A, B);
437%! assert (C'*C + S'*S, eye (5), 5*eps);
438%! assert (U*C*X', A, 10*eps);
439%! assert (V*S*X', B, 85*eps);
440
441## Test that single inputs produce single outputs
442%!test
443%! s = gsvd (single (eye (5)), B);
444%! assert (class (s), "single");
445%! [U,V,X,C,S] = gsvd (single (eye(5)), B);
446%! assert (class (U), "single");
447%! assert (class (V), "single");
448%! assert (class (X), "single");
449%! assert (class (C), "single");
450%! assert (class (S), "single");
451%!
452%! s = gsvd (A, single (eye (5)));
453%! assert (class (s), "single");
454%! [U,V,X,C,S] = gsvd (A, single (eye (5)));
455%! assert (class (U), "single");
456%! assert (class (V), "single");
457%! assert (class (X), "single");
458%! assert (class (C), "single");
459%! assert (class (S), "single");
460
461## Test input validation
462%!error <Invalid call> gsvd ()
463%!error <Invalid call> gsvd (1)
464%!error <Invalid call> gsvd (1,2,3,4)
465%!warning <economy-sized decomposition is not yet implemented> gsvd (1,2,0);
466%!error <A and B must have the same number of columns> gsvd (1,[1, 2])
467## Test input validation for single (real and complex) inputs.
468%!error <A cannot have Inf or NaN values> gsvd (Inf, single (2))
469%!error <A cannot have Inf or NaN values> gsvd (NaN, single (2))
470%!error <B cannot have Inf or NaN values> gsvd (single (1), Inf)
471%!error <B cannot have Inf or NaN values> gsvd (single (1), NaN)
472%!error <A must be a real or complex matrix> gsvd ({1}, single (2i))
473%!error <B must be a real or complex matrix> gsvd (single (i), {2})
474%!error <A cannot have Inf or NaN values> gsvd (Inf, single (2i))
475%!error <A cannot have Inf or NaN values> gsvd (NaN, single (2i))
476%!error <B cannot have Inf or NaN values> gsvd (single (i), Inf)
477%!error <B cannot have Inf or NaN values> gsvd (single (i), NaN)
478## Test input validation for single, but not real or complex, inputs.
479%!error <A and B must be real or complex matrices> gsvd ({1}, single (2))
480%!error <A and B must be real or complex matrices> gsvd (single (1), {2})
481## Test input validation for double (real and complex) inputs.
482%!error <A cannot have Inf or NaN values> gsvd (Inf, 2)
483%!error <A cannot have Inf or NaN values> gsvd (NaN, 2)
484%!error <B cannot have Inf or NaN values> gsvd (1, Inf)
485%!error <B cannot have Inf or NaN values> gsvd (1, NaN)
486%!error <A must be a real or complex matrix> gsvd ({1}, 2i)
487%!error <B must be a real or complex matrix> gsvd (i, {2})
488%!error <A cannot have Inf or NaN values> gsvd (Inf, 2i)
489%!error <A cannot have Inf or NaN values> gsvd (NaN, 2i)
490%!error <B cannot have Inf or NaN values> gsvd (i, Inf)
491%!error <B cannot have Inf or NaN values> gsvd (i, NaN)
492## Test input validation for double, but not real or complex, inputs.
493%!error <A and B must be real or complex matrices> gsvd ({1}, double (2))
494%!error <A and B must be real or complex matrices> gsvd (double (1), {2})
495## Test input validation in liboctave/numeric/gsvd.cc
496%!error <A and B cannot be empty matrices> gsvd (zeros (0,1), 1)
497%!error <A and B cannot be empty matrices> gsvd (1, zeros (0,1))
498
499*/
500
501OCTAVE_NAMESPACE_END
T & xelem(octave_idx_type n)
Size of the specified dimension.
Definition: Array.h:504
octave_idx_type rows(void) const
Definition: Array.h:449
OCTARRAY_API Array< T, Alloc > sort(int dim=0, sortmode mode=ASCENDING) const
Size of the specified dimension.
Definition: Array.cc:1761
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: mx-defs.h:43
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
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:1055
void error(const char *fmt,...)
Definition: error.cc:980
static OCTAVE_NAMESPACE_BEGIN 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