GNU Octave  9.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-2024 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 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 
519 OCTAVE_END_NAMESPACE(octave)
octave_idx_type rows() const
Definition: Array.h:459
Array< T, Alloc > sort(int dim=0, sortmode mode=ASCENDING) const
Size of the specified dimension.
Definition: Array-base.cc:1781
T & xelem(octave_idx_type n)
Size of the specified dimension.
Definition: Array.h:524
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
Definition: dMatrix.h:42
bool any_element_is_inf_or_nan() const
Definition: dNDArray.cc:324
Definition: gsvd.h:39
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(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:1063
void() error(const char *fmt,...)
Definition: error.cc:988
F77_RET_T const F77_INT F77_CMPLX const F77_INT F77_CMPLX * B
F77_RET_T const F77_INT F77_CMPLX * A