GNU Octave 10.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
 
Loading...
Searching...
No Matches
gsvd.cc
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////
2//
3// Copyright (C) 1997-2025 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#if defined (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
45template <typename T>
46static typename math::gsvd<T>::Type
47gsvd_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.
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 =
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 A0, B0
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
293## A (5x3) and B (3x3) are full rank
294%!test <48807>
295%! A = A0;
296%! B = B0;
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%! A = A0;
305%! B = B0;
306%! B(2, 2) = 0;
307%! [U, V, X, C, S] = gsvd (A, B);
308%! assert (C'*C + S'*S, eye (3), 5*eps);
309%! assert (U*C*X', A, 10*eps);
310%! assert (V*S*X', B, 20*eps);
311
312## A: 5x3 rank deficient, B: 3x3 full rank
313%!test <48807>
314%! A = A0;
315%! B = B0;
316%! A(:, 3) = 2*A(:, 1) - A(:, 2);
317%! [U, V, X, C, S] = gsvd (A, B);
318%! assert (C'*C + S'*S, eye (3), 5*eps);
319%! assert (U*C*X', A, 10*eps);
320%! assert (V*S*X', B, 20*eps);
321
322## A and B are both rank deficient
323## FIXME: LAPACK seems to be completely broken for this case
324%!#test <48807>
325%! A = A0;
326%! B = B0;
327%! B(:, 3) = 2*B(:, 1) - B(:, 2);
328%! [U, V, X, C, S] = gsvd (A, B);
329%! assert (C'*C + S'*S, eye (3), 5*eps);
330%! assert (U*C*X', A, 10*eps);
331%! assert (V*S*X', B, 20*eps);
332
333## A (now 3x5) and B (now 5x5) are full rank
334%!test <48807>
335%! A = A0.';
336%! B = diag ([1 2 4 8 16]);
337%! [U, V, X, C, S] = gsvd (A, B);
338%! assert (C'*C + S'*S, eye (5), 5*eps);
339%! assert (U*C*X', A, 15*eps);
340%! assert (V*S*X', B, 85*eps);
341
342## A: 3x5 full rank, B: 5x5 rank deficient
343%!test <48807>
344%! A = A0.';
345%! B = diag ([1 2 4 8 16]);
346%! B(2, 2) = 0;
347%! [U, V, X, C, S] = gsvd (A, B);
348%! assert (C'*C + S'*S, eye (5), 5*eps);
349%! assert (U*C*X', A, 15*eps);
350%! assert (V*S*X', B, 85*eps);
351
352## A: 3x5 rank deficient, B: 5x5 full rank
353%!test <48807>
354%! A = A0.';
355%! B = diag ([1 2 4 8 16]);
356%! A(3, :) = 2*A(1, :) - A(2, :);
357%! [U, V, X, C, S] = gsvd (A, B);
358%! assert (C'*C + S'*S, eye (5), 5*eps);
359%! assert (U*C*X', A, 15*eps);
360%! assert (V*S*X', B, 85*eps);
361
362## A and B are both rank deficient
363## FIXME: LAPACK seems to be completely broken for this case
364%!#test <48807>
365%! A = A0.'; B = B0.';
366%! A(:, 3) = 2*A(:, 1) - A(:, 2);
367%! B(:, 3) = 2*B(:, 1) - B(:, 2);
368%! [U, V, X, C, S] = gsvd (A, B);
369%! assert (C'*C + S'*S, eye (3), 5*eps);
370%! assert (U*C*X', A, 10*eps);
371%! assert (V*S*X', B, 20*eps);
372
373## A: 5x3 complex full rank, B: 3x3 complex full rank
374%!test <48807>
375%! old_state = randn ("state");
376%! restore_state = onCleanup (@() randn ("state", old_state));
377%! randn ("state", 12345); # initialize generator to make behavior reproducible
378%! A = A0 + j* randn (5, 3);
379%! B = diag ([1 2 4]) + j* diag ([4 -2 -1]);
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 full rank, B: 3x3 complex rank deficient
386%!test <48807>
387%! A = A0 + j* randn (5, 3);
388%! B = diag ([1 2 4]) + j* diag ([4 -2 -1]);
389%! B(2, 2) = 0;
390%! [U, V, X, C, S] = gsvd (A, B);
391%! assert (C'*C + S'*S, eye (3), 5*eps);
392%! assert (U*C*X', A, 10*eps);
393%! assert (V*S*X', B, 25*eps);
394
395## A: 5x3 complex rank deficient, B: 3x3 complex full rank
396%!test <48807>
397%! A = A0 + j* randn (5, 3);
398%! B = diag ([1 2 4]) + j* diag ([4 -2 -1]);
399%! A(:, 3) = 2*A(:, 1) - A(:, 2);
400%! [U, V, X, C, S] = gsvd (A, B);
401%! assert (C'*C + S'*S, eye (3), 5*eps);
402%! assert (U*C*X', A, 15*eps);
403%! assert (V*S*X', B, 25*eps);
404
405## A (5x3) and B (3x3) are both complex rank deficient
406## FIXME: LAPACK seems to be completely broken for this case
407%!#test <48807>
408%! A = A0 + j* randn (5, 3);
409%! B = diag ([1 2 4]) + j* diag ([4 -2 -1]);
410%! B(:, 3) = 2*B(:, 1) - B(:, 2);
411%! [U, V, X, C, S] = gsvd (A, B);
412%! assert (C'*C + S'*S, eye (3), 5*eps);
413%! assert (U*C*X', A, 10*eps);
414%! assert (V*S*X', B, 20*eps);
415
416## A (now 3x5) complex and B (now 5x5) complex are full rank
417## now, A is 3x5
418%!test <48807>
419%! A = A0.';
420%! B = diag ([1 2 4 8 16]) + j* diag ([-5 4 -3 2 -1]);
421%! [U, V, X, C, S] = gsvd (A, B);
422%! assert (C'*C + S'*S, eye (5), 5*eps);
423%! assert (U*C*X', A, 25*eps);
424%! assert (V*S*X', B, 85*eps);
425
426## A: 3x5 complex full rank, B: 5x5 complex rank deficient
427%!test <48807>
428%! A = A0.';
429%! B = diag ([1 0 4 8 16]) + j* diag ([-5 0 -3 2 -1]);
430%! [U, V, X, C, S] = gsvd (A, B);
431%! assert (C'*C + S'*S, eye (5), 5*eps);
432%! assert (U*C*X', A, 10*eps);
433%! assert (V*S*X', B, 85*eps);
434
435## A: 3x5 complex rank deficient, B: 5x5 complex full rank
436%!test <48807>
437%! A = A0.';
438%! B = diag ([1 2 4 8 16]) + j* diag ([-5 4 -3 2 -1]);
439%! A(3, :) = 2*A(1, :) - A(2, :);
440%! [U, V, X, C, S] = gsvd (A, B);
441%! assert (C'*C + S'*S, eye (5), 5*eps);
442%! assert (U*C*X', A, 10*eps);
443%! assert (V*S*X', B, 85*eps);
444
445## A and B are both complex rank deficient
446## FIXME: LAPACK seems to be completely broken for this case
447%!#test <48807>
448%! A = A0.';
449%! B = B0.';
450%! A(:, 3) = 2*A(:, 1) - A(:, 2);
451%! B(:, 3) = 2*B(:, 1) - B(:, 2);
452%! [U, V, X, C, S] = gsvd (A, B);
453%! assert (C'*C + S'*S, eye (5), 5*eps);
454%! assert (U*C*X', A, 10*eps);
455%! assert (V*S*X', B, 85*eps);
456
457## Test that single inputs produce single outputs
458%!test
459%! A = A0.';
460%! B = diag ([1 2 4 8 16]) + j* diag ([-5 4 -3 2 -1]);
461%! s = gsvd (single (eye (5)), B);
462%! assert (class (s), "single");
463%! [U, V, X, C, S] = gsvd (single (eye(5)), B);
464%! assert (class (U), "single");
465%! assert (class (V), "single");
466%! assert (class (X), "single");
467%! assert (class (C), "single");
468%! assert (class (S), "single");
469%!
470%! s = gsvd (A, single (eye (5)));
471%! assert (class (s), "single");
472%! [U, V, X, C, S] = gsvd (A, single (eye (5)));
473%! assert (class (U), "single");
474%! assert (class (V), "single");
475%! assert (class (X), "single");
476%! assert (class (C), "single");
477%! assert (class (S), "single");
478
479## Test input validation
480%!error <Invalid call> gsvd ()
481%!error <Invalid call> gsvd (1)
482%!error <Invalid call> gsvd (1,2,3,4)
483%!warning <economy-sized decomposition is not yet implemented> gsvd (1,2,0);
484%!error <A and B must have the same number of columns> gsvd (1,[1, 2])
485## Test input validation for single (real and complex) inputs.
486%!error <A cannot have Inf or NaN values> gsvd (Inf, single (2))
487%!error <A cannot have Inf or NaN values> gsvd (NaN, single (2))
488%!error <B cannot have Inf or NaN values> gsvd (single (1), Inf)
489%!error <B cannot have Inf or NaN values> gsvd (single (1), NaN)
490%!error <A must be a real or complex matrix> gsvd ({1}, single (2i))
491%!error <B must be a real or complex matrix> gsvd (single (i), {2})
492%!error <A cannot have Inf or NaN values> gsvd (Inf, single (2i))
493%!error <A cannot have Inf or NaN values> gsvd (NaN, single (2i))
494%!error <B cannot have Inf or NaN values> gsvd (single (i), Inf)
495%!error <B cannot have Inf or NaN values> gsvd (single (i), NaN)
496## Test input validation for single, but not real or complex, inputs.
497%!error <A and B must be real or complex matrices> gsvd ({1}, single (2))
498%!error <A and B must be real or complex matrices> gsvd (single (1), {2})
499## Test input validation for double (real and complex) inputs.
500%!error <A cannot have Inf or NaN values> gsvd (Inf, 2)
501%!error <A cannot have Inf or NaN values> gsvd (NaN, 2)
502%!error <B cannot have Inf or NaN values> gsvd (1, Inf)
503%!error <B cannot have Inf or NaN values> gsvd (1, NaN)
504%!error <A must be a real or complex matrix> gsvd ({1}, 2i)
505%!error <B must be a real or complex matrix> gsvd (i, {2})
506%!error <A cannot have Inf or NaN values> gsvd (Inf, 2i)
507%!error <A cannot have Inf or NaN values> gsvd (NaN, 2i)
508%!error <B cannot have Inf or NaN values> gsvd (i, Inf)
509%!error <B cannot have Inf or NaN values> gsvd (i, NaN)
510## Test input validation for double, but not real or complex, inputs.
511%!error <A and B must be real or complex matrices> gsvd ({1}, double (2))
512%!error <A and B must be real or complex matrices> gsvd (double (1), {2})
513## Test input validation in liboctave/numeric/gsvd.cc
514%!error <A and B cannot be empty matrices> gsvd (zeros (0,1), 1)
515%!error <A and B cannot be empty matrices> gsvd (1, zeros (0,1))
516
517*/
518
519OCTAVE_END_NAMESPACE(octave)
T & xelem(octave_idx_type n)
Size of the specified dimension.
Definition Array.h:525
octave_idx_type rows() const
Definition Array.h:463
Array< T, Alloc > sort(int dim=0, sortmode mode=ASCENDING) const
Size of the specified dimension.
bool any_element_is_inf_or_nan() const
Definition CNDArray.cc:271
bool any_element_is_inf_or_nan() const
Definition fCNDArray.cc:271
bool any_element_is_inf_or_nan() const
Definition fNDArray.cc:281
bool any_element_is_inf_or_nan() const
Definition dNDArray.cc:324
Definition gsvd.h:37
bool isreal() const
Definition ov.h:738
FloatMatrix xfloat_matrix_value(const char *fmt,...) const
ComplexMatrix xcomplex_matrix_value(const char *fmt,...) const
bool is_single_type() const
Definition ov.h:698
FloatComplexMatrix xfloat_complex_matrix_value(const char *fmt,...) const
bool iscomplex() const
Definition ov.h:741
Matrix xmatrix_value(const char *fmt,...) const
octave_idx_type columns() const
Definition ov.h:547
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
void print_usage()
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:1078
void error(const char *fmt,...)
Definition error.cc:1003
F77_RET_T const F77_INT F77_CMPLX const F77_INT F77_CMPLX * B
F77_RET_T const F77_INT F77_CMPLX * A