GNU Octave 11.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-2026 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@tex
119$$ A = U C X^\dagger $$
120$$ B = V S X^\dagger $$
121$$ C^\dagger C + S^\dagger S = eye (columns (A)) $$
122@end tex
123@ifnottex
124
125@example
126@group
127A = U*C*X'
128B = V*S*X'
129C'*C + S'*S = eye (columns (A))
130@end group
131@end example
132
133@end ifnottex
134
135The function @code{gsvd} normally returns just the vector of generalized
136singular values
137@tex
138$$ \sqrt{{{diag (C^\dagger C)} \over {diag (S^\dagger S)}}} $$
139@end tex
140@ifnottex
141@code{sqrt (diag (C'*C) ./ diag (S'*S))}.
142@end ifnottex
143If asked for five return values, it also computes
144@tex
145$U$, $V$, $X$, and $C$.
146@end tex
147@ifnottex
148U, V, X, and C.
149@end ifnottex
150
151If the optional third input is present, @code{gsvd} constructs the
152"economy-sized" decomposition where the number of columns of @var{U}, @var{V}
153and the number of rows of @var{C}, @var{S} is less than or equal to the number
154of columns of @var{A}. This option is not yet implemented.
155
156Programming Note: the code is a wrapper to the corresponding @sc{lapack} dggsvd
157and zggsvd routines. If matrices @var{A} and @var{B} are @emph{both} rank
158deficient then @sc{lapack} will return an incorrect factorization. Programmers
159should avoid this combination.
160@seealso{svd}
161@end deftypefn */)
162{
163 int nargin = args.length ();
164
165 if (nargin < 2 || nargin > 3)
166 print_usage ();
167 else if (nargin == 3)
168 {
169 // FIXME: when "economy" is implemented delete this code
170 warning ("gsvd: economy-sized decomposition is not yet implemented, returning full decomposition");
171 nargin = 2;
172 }
173
174 octave_value_list retval;
175
176 octave_value argA = args(0);
177 octave_value argB = args(1);
178
179 if (argA.columns () != argB.columns ())
180 error ("gsvd: A and B must have the same number of columns");
181
182 if (argA.is_single_type () || argB.is_single_type ())
183 {
184 if (argA.isreal () && argB.isreal ())
185 {
186 FloatMatrix tmpA = argA.xfloat_matrix_value ("gsvd: A must be a real or complex matrix");
187 FloatMatrix tmpB = argB.xfloat_matrix_value ("gsvd: B must be a real or complex matrix");
188
189 if (tmpA.any_element_is_inf_or_nan ())
190 error ("gsvd: A cannot have Inf or NaN values");
191 if (tmpB.any_element_is_inf_or_nan ())
192 error ("gsvd: B cannot have Inf or NaN values");
193
194 retval = do_gsvd (tmpA, tmpB, nargout, nargin, true);
195 }
196 else if (argA.iscomplex () || argB.iscomplex ())
197 {
198 FloatComplexMatrix ctmpA =
199 argA.xfloat_complex_matrix_value ("gsvd: A must be a real or complex matrix");
200 FloatComplexMatrix ctmpB =
201 argB.xfloat_complex_matrix_value ("gsvd: B must be a real or complex matrix");
202
203 if (ctmpA.any_element_is_inf_or_nan ())
204 error ("gsvd: A cannot have Inf or NaN values");
205 if (ctmpB.any_element_is_inf_or_nan ())
206 error ("gsvd: B cannot have Inf or NaN values");
207
208 retval = do_gsvd (ctmpA, ctmpB, nargout, nargin, true);
209 }
210 else
211 error ("gsvd: A and B must be real or complex matrices");
212 }
213 else
214 {
215 if (argA.isreal () && argB.isreal ())
216 {
217 Matrix tmpA = argA.xmatrix_value ("gsvd: A must be a real or complex matrix");
218 Matrix tmpB = argB.xmatrix_value ("gsvd: B must be a real or complex matrix");
219
220 if (tmpA.any_element_is_inf_or_nan ())
221 error ("gsvd: A cannot have Inf or NaN values");
222 if (tmpB.any_element_is_inf_or_nan ())
223 error ("gsvd: B cannot have Inf or NaN values");
224
225 retval = do_gsvd (tmpA, tmpB, nargout, nargin);
226 }
227 else if (argA.iscomplex () || argB.iscomplex ())
228 {
229 ComplexMatrix ctmpA = argA.xcomplex_matrix_value ("gsvd: A must be a real or complex matrix");
230 ComplexMatrix ctmpB = argB.xcomplex_matrix_value ("gsvd: B must be a real or complex matrix");
231
232 if (ctmpA.any_element_is_inf_or_nan ())
233 error ("gsvd: A cannot have Inf or NaN values");
234 if (ctmpB.any_element_is_inf_or_nan ())
235 error ("gsvd: B cannot have Inf or NaN values");
236
237 retval = do_gsvd (ctmpA, ctmpB, nargout, nargin);
238 }
239 else
240 error ("gsvd: A and B must be real or complex matrices");
241 }
242
243 return retval;
244}
245
246/*
247
248## Basic tests of decomposition
249%!test <60273>
250%! A = reshape (1:15,5,3);
251%! B = magic (3);
252%! [U,V,X,C,S] = gsvd (A,B);
253%! assert (size (U), [5, 5]);
254%! assert (size (V), [3, 3]);
255%! assert (size (X), [3, 3]);
256%! assert (size (C), [5, 3]);
257%! assert (C(4:5, :), zeros (2,3));
258%! assert (size (S), [3, 3]);
259%! assert (U*C*X', A, 50*eps);
260%! assert (V*S*X', B, 50*eps);
261%! S0 = gsvd (A, B);
262%! assert (size (S0), [3, 1]);
263%! S1 = sort (svd (A / B));
264%! assert (S0, S1, 10*eps);
265
266%!test <60273>
267%! A = reshape (1:15,3,5);
268%! B = magic (5);
269%! [U,V,X,C,S] = gsvd (A,B);
270%! assert (size (U), [3, 3]);
271%! assert (size (V), [5, 5]);
272%! assert (size (X), [5, 5]);
273%! assert (size (C), [3, 5]);
274%! assert (C(:, 4:5), zeros (3,2));
275%! assert (size (S), [5, 5]);
276%! assert (U*C*X', A, 120*eps); # less accurate in this orientation
277%! assert (V*S*X', B, 150*eps); # for some reason.
278%! S0 = gsvd (A, B);
279%! assert (size (S0), [5, 1]);
280%! S0 = S0(3:end);
281%! S1 = sort (svd (A / B));
282%! assert (S0, S1, 20*eps);
283
284## a few tests for gsvd.m
285%!shared A0, B0
286%! old_state = randn ("state");
287%! restore_state = onCleanup (@() randn ("state", old_state));
288%! randn ("state", 40); # initialize generator to make behavior reproducible
289%! A0 = randn (5, 3);
290%! B0 = diag ([1 2 4]);
291
292## A (5x3) and B (3x3) are full rank
293%!test <48807>
294%! A = A0;
295%! B = B0;
296%! [U, V, X, C, S] = gsvd (A, B);
297%! assert (C'*C + S'*S, eye (3), 5*eps);
298%! assert (U*C*X', A, 10*eps);
299%! assert (V*S*X', B, 20*eps);
300
301## A: 5x3 full rank, B: 3x3 rank deficient
302%!test <48807>
303%! A = A0;
304%! B = B0;
305%! B(2, 2) = 0;
306%! [U, V, X, C, S] = gsvd (A, B);
307%! assert (C'*C + S'*S, eye (3), 5*eps);
308%! assert (U*C*X', A, 10*eps);
309%! assert (V*S*X', B, 20*eps);
310
311## A: 5x3 rank deficient, B: 3x3 full rank
312%!test <48807>
313%! A = A0;
314%! B = B0;
315%! A(:, 3) = 2*A(:, 1) - A(:, 2);
316%! [U, V, X, C, S] = gsvd (A, B);
317%! assert (C'*C + S'*S, eye (3), 5*eps);
318%! assert (U*C*X', A, 10*eps);
319%! assert (V*S*X', B, 20*eps);
320
321## A and B are both rank deficient
322## FIXME: LAPACK seems to be completely broken for this case
323%!#test <48807>
324%! A = A0;
325%! B = B0;
326%! B(:, 3) = 2*B(:, 1) - B(:, 2);
327%! [U, V, X, C, S] = gsvd (A, B);
328%! assert (C'*C + S'*S, eye (3), 5*eps);
329%! assert (U*C*X', A, 10*eps);
330%! assert (V*S*X', B, 20*eps);
331
332## A (now 3x5) and B (now 5x5) are full rank
333%!test <48807>
334%! A = A0.';
335%! B = diag ([1 2 4 8 16]);
336%! [U, V, X, C, S] = gsvd (A, B);
337%! assert (C'*C + S'*S, eye (5), 5*eps);
338%! assert (U*C*X', A, 15*eps);
339%! assert (V*S*X', B, 85*eps);
340
341## A: 3x5 full rank, B: 5x5 rank deficient
342%!test <48807>
343%! A = A0.';
344%! B = diag ([1 2 4 8 16]);
345%! B(2, 2) = 0;
346%! [U, V, X, C, S] = gsvd (A, B);
347%! assert (C'*C + S'*S, eye (5), 5*eps);
348%! assert (U*C*X', A, 15*eps);
349%! assert (V*S*X', B, 85*eps);
350
351## A: 3x5 rank deficient, B: 5x5 full rank
352%!test <48807>
353%! A = A0.';
354%! B = diag ([1 2 4 8 16]);
355%! A(3, :) = 2*A(1, :) - A(2, :);
356%! [U, V, X, C, S] = gsvd (A, B);
357%! assert (C'*C + S'*S, eye (5), 5*eps);
358%! assert (U*C*X', A, 15*eps);
359%! assert (V*S*X', B, 85*eps);
360
361## A and B are both rank deficient
362## FIXME: LAPACK seems to be completely broken for this case
363%!#test <48807>
364%! A = A0.'; B = B0.';
365%! A(:, 3) = 2*A(:, 1) - A(:, 2);
366%! B(:, 3) = 2*B(:, 1) - B(:, 2);
367%! [U, V, X, C, S] = gsvd (A, B);
368%! assert (C'*C + S'*S, eye (3), 5*eps);
369%! assert (U*C*X', A, 10*eps);
370%! assert (V*S*X', B, 20*eps);
371
372## A: 5x3 complex full rank, B: 3x3 complex full rank
373%!test <48807>
374%! old_state = randn ("state");
375%! restore_state = onCleanup (@() randn ("state", old_state));
376%! randn ("state", 12345); # initialize generator to make behavior reproducible
377%! A = A0 + j* randn (5, 3);
378%! B = diag ([1 2 4]) + j* diag ([4 -2 -1]);
379%! [U, V, X, C, S] = gsvd (A, B);
380%! assert (C'*C + S'*S, eye (3), 5*eps);
381%! assert (U*C*X', A, 10*eps);
382%! assert (V*S*X', B, 25*eps);
383
384## A: 5x3 complex full rank, B: 3x3 complex rank deficient
385%!test <48807>
386%! A = A0 + j* randn (5, 3);
387%! B = diag ([1 2 4]) + j* diag ([4 -2 -1]);
388%! B(2, 2) = 0;
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, 10*eps);
392%! assert (V*S*X', B, 25*eps);
393
394## A: 5x3 complex rank deficient, B: 3x3 complex full rank
395%!test <48807>
396%! A = A0 + j* randn (5, 3);
397%! B = diag ([1 2 4]) + j* diag ([4 -2 -1]);
398%! A(:, 3) = 2*A(:, 1) - A(:, 2);
399%! [U, V, X, C, S] = gsvd (A, B);
400%! assert (C'*C + S'*S, eye (3), 5*eps);
401%! assert (U*C*X', A, 15*eps);
402%! assert (V*S*X', B, 25*eps);
403
404## A (5x3) and B (3x3) are both complex rank deficient
405## FIXME: LAPACK seems to be completely broken for this case
406%!#test <48807>
407%! A = A0 + j* randn (5, 3);
408%! B = diag ([1 2 4]) + j* diag ([4 -2 -1]);
409%! B(:, 3) = 2*B(:, 1) - B(:, 2);
410%! [U, V, X, C, S] = gsvd (A, B);
411%! assert (C'*C + S'*S, eye (3), 5*eps);
412%! assert (U*C*X', A, 10*eps);
413%! assert (V*S*X', B, 20*eps);
414
415## A (now 3x5) complex and B (now 5x5) complex are full rank
416## now, A is 3x5
417%!test <48807>
418%! A = A0.';
419%! B = diag ([1 2 4 8 16]) + j* diag ([-5 4 -3 2 -1]);
420%! [U, V, X, C, S] = gsvd (A, B);
421%! assert (C'*C + S'*S, eye (5), 5*eps);
422%! assert (U*C*X', A, 25*eps);
423%! assert (V*S*X', B, 85*eps);
424
425## A: 3x5 complex full rank, B: 5x5 complex rank deficient
426%!test <48807>
427%! A = A0.';
428%! B = diag ([1 0 4 8 16]) + j* diag ([-5 0 -3 2 -1]);
429%! [U, V, X, C, S] = gsvd (A, B);
430%! assert (C'*C + S'*S, eye (5), 5*eps);
431%! assert (U*C*X', A, 10*eps);
432%! assert (V*S*X', B, 85*eps);
433
434## A: 3x5 complex rank deficient, B: 5x5 complex full rank
435%!test <48807>
436%! A = A0.';
437%! B = diag ([1 2 4 8 16]) + j* diag ([-5 4 -3 2 -1]);
438%! A(3, :) = 2*A(1, :) - A(2, :);
439%! [U, V, X, C, S] = gsvd (A, B);
440%! assert (C'*C + S'*S, eye (5), 5*eps);
441%! assert (U*C*X', A, 10*eps);
442%! assert (V*S*X', B, 85*eps);
443
444## A and B are both complex rank deficient
445## FIXME: LAPACK seems to be completely broken for this case
446%!#test <48807>
447%! A = A0.';
448%! B = B0.';
449%! A(:, 3) = 2*A(:, 1) - A(:, 2);
450%! B(:, 3) = 2*B(:, 1) - B(:, 2);
451%! [U, V, X, C, S] = gsvd (A, B);
452%! assert (C'*C + S'*S, eye (5), 5*eps);
453%! assert (U*C*X', A, 10*eps);
454%! assert (V*S*X', B, 85*eps);
455
456## Test that single inputs produce single outputs
457%!test
458%! A = A0.';
459%! B = diag ([1 2 4 8 16]) + j* diag ([-5 4 -3 2 -1]);
460%! s = gsvd (single (eye (5)), B);
461%! assert (class (s), "single");
462%! [U, V, X, C, S] = gsvd (single (eye(5)), B);
463%! assert (class (U), "single");
464%! assert (class (V), "single");
465%! assert (class (X), "single");
466%! assert (class (C), "single");
467%! assert (class (S), "single");
468%!
469%! s = gsvd (A, single (eye (5)));
470%! assert (class (s), "single");
471%! [U, V, X, C, S] = gsvd (A, single (eye (5)));
472%! assert (class (U), "single");
473%! assert (class (V), "single");
474%! assert (class (X), "single");
475%! assert (class (C), "single");
476%! assert (class (S), "single");
477
478## Test input validation
479%!error <Invalid call> gsvd ()
480%!error <Invalid call> gsvd (1)
481%!error <Invalid call> gsvd (1,2,3,4)
482%!warning <economy-sized decomposition is not yet implemented> gsvd (1,2,0);
483%!error <A and B must have the same number of columns> gsvd (1,[1, 2])
484## Test input validation for single (real and complex) inputs.
485%!error <A cannot have Inf or NaN values> gsvd (Inf, single (2))
486%!error <A cannot have Inf or NaN values> gsvd (NaN, single (2))
487%!error <B cannot have Inf or NaN values> gsvd (single (1), Inf)
488%!error <B cannot have Inf or NaN values> gsvd (single (1), NaN)
489%!error <A must be a real or complex matrix> gsvd ({1}, single (2i))
490%!error <B must be a real or complex matrix> gsvd (single (i), {2})
491%!error <A cannot have Inf or NaN values> gsvd (Inf, single (2i))
492%!error <A cannot have Inf or NaN values> gsvd (NaN, single (2i))
493%!error <B cannot have Inf or NaN values> gsvd (single (i), Inf)
494%!error <B cannot have Inf or NaN values> gsvd (single (i), NaN)
495## Test input validation for single, but not real or complex, inputs.
496%!error <A and B must be real or complex matrices> gsvd ({1}, single (2))
497%!error <A and B must be real or complex matrices> gsvd (single (1), {2})
498## Test input validation for double (real and complex) inputs.
499%!error <A cannot have Inf or NaN values> gsvd (Inf, 2)
500%!error <A cannot have Inf or NaN values> gsvd (NaN, 2)
501%!error <B cannot have Inf or NaN values> gsvd (1, Inf)
502%!error <B cannot have Inf or NaN values> gsvd (1, NaN)
503%!error <A must be a real or complex matrix> gsvd ({1}, 2i)
504%!error <B must be a real or complex matrix> gsvd (i, {2})
505%!error <A cannot have Inf or NaN values> gsvd (Inf, 2i)
506%!error <A cannot have Inf or NaN values> gsvd (NaN, 2i)
507%!error <B cannot have Inf or NaN values> gsvd (i, Inf)
508%!error <B cannot have Inf or NaN values> gsvd (i, NaN)
509## Test input validation for double, but not real or complex, inputs.
510%!error <A and B must be real or complex matrices> gsvd ({1}, double (2))
511%!error <A and B must be real or complex matrices> gsvd (double (1), {2})
512## Test input validation in liboctave/numeric/gsvd.cc
513%!error <A and B cannot be empty matrices> gsvd (zeros (0,1), 1)
514%!error <A and B cannot be empty matrices> gsvd (1, zeros (0,1))
515
516*/
517
518OCTAVE_END_NAMESPACE(octave)
T & xelem(octave_idx_type n)
Size of the specified dimension.
Definition Array-base.h:547
octave_idx_type rows() const
Definition Array-base.h:485
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:36
bool isreal() const
Definition ov.h:736
FloatMatrix xfloat_matrix_value(const char *fmt,...) const
ComplexMatrix xcomplex_matrix_value(const char *fmt,...) const
bool is_single_type() const
Definition ov.h:696
FloatComplexMatrix xfloat_complex_matrix_value(const char *fmt,...) const
bool iscomplex() const
Definition ov.h:739
Matrix xmatrix_value(const char *fmt,...) const
octave_idx_type columns() const
Definition ov.h:545
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:1083
void error(const char *fmt,...)
Definition error.cc:1008
F77_RET_T const F77_INT F77_CMPLX const F77_INT F77_CMPLX * B
F77_RET_T const F77_INT F77_CMPLX * A