1 // SPDX-License-Identifier: Apache-2.0
2 //
3 // Copyright 2008-2016 Conrad Sanderson (http://conradsanderson.id.au)
4 // Copyright 2008-2016 National ICT Australia (NICTA)
5 //
6 // Licensed under the Apache License, Version 2.0 (the "License");
7 // you may not use this file except in compliance with the License.
8 // You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 // ------------------------------------------------------------------------
17
18
19 //! \addtogroup op_logmat
20 //! @{
21
22
23 // Partly based on algorithm 11.9 (inverse scaling and squaring algorithm with Schur decomposition) in:
24 // Nicholas J. Higham.
25 // Functions of Matrices: Theory and Computation.
26 // SIAM, 2008.
27 // ISBN 978-0-89871-646-7
28
29
30 template<typename T1>
31 inline
32 void
apply(Mat<std::complex<typename T1::elem_type>> & out,const mtOp<std::complex<typename T1::elem_type>,T1,op_logmat> & in)33 op_logmat::apply(Mat< std::complex<typename T1::elem_type> >& out, const mtOp<std::complex<typename T1::elem_type>,T1,op_logmat>& in)
34 {
35 arma_extra_debug_sigprint();
36
37 const bool status = op_logmat::apply_direct(out, in.m, in.aux_uword_a);
38
39 if(status == false)
40 {
41 out.soft_reset();
42 arma_stop_runtime_error("logmat(): transformation failed");
43 }
44 }
45
46
47
48 template<typename T1>
49 inline
50 bool
apply_direct(Mat<std::complex<typename T1::elem_type>> & out,const Op<T1,op_diagmat> & expr,const uword)51 op_logmat::apply_direct(Mat< std::complex<typename T1::elem_type> >& out, const Op<T1,op_diagmat>& expr, const uword)
52 {
53 arma_extra_debug_sigprint();
54
55 typedef typename T1::elem_type T;
56
57 const diagmat_proxy<T1> P(expr.m);
58
59 arma_debug_check( (P.n_rows != P.n_cols), "logmat(): given matrix must be square sized" );
60
61 const uword N = P.n_rows;
62
63 out.zeros(N,N); // aliasing can't happen as op_logmat is defined as cx_mat = op(mat)
64
65 for(uword i=0; i<N; ++i)
66 {
67 const T val = P[i];
68
69 if(val >= T(0))
70 {
71 out.at(i,i) = std::log(val);
72 }
73 else
74 {
75 out.at(i,i) = std::log( std::complex<T>(val) );
76 }
77 }
78
79 return true;
80 }
81
82
83
84 template<typename T1>
85 inline
86 bool
apply_direct(Mat<std::complex<typename T1::elem_type>> & out,const Base<typename T1::elem_type,T1> & expr,const uword n_iters)87 op_logmat::apply_direct(Mat< std::complex<typename T1::elem_type> >& out, const Base<typename T1::elem_type,T1>& expr, const uword n_iters)
88 {
89 arma_extra_debug_sigprint();
90
91 typedef typename T1::elem_type in_T;
92 typedef typename std::complex<in_T> out_T;
93
94 const quasi_unwrap<T1> expr_unwrap(expr.get_ref());
95 const Mat<in_T>& A = expr_unwrap.M;
96
97 arma_debug_check( (A.is_square() == false), "logmat(): given matrix must be square sized" );
98
99 if(A.n_elem == 0)
100 {
101 out.reset();
102 return true;
103 }
104 else
105 if(A.n_elem == 1)
106 {
107 out.set_size(1,1);
108 out[0] = std::log( std::complex<in_T>( A[0] ) );
109 return true;
110 }
111
112 if(A.is_diagmat())
113 {
114 const uword N = A.n_rows;
115
116 out.zeros(N,N); // aliasing can't happen as op_logmat is defined as cx_mat = op(mat)
117
118 for(uword i=0; i<N; ++i)
119 {
120 const in_T val = A.at(i,i);
121
122 if(val >= in_T(0))
123 {
124 out.at(i,i) = std::log(val);
125 }
126 else
127 {
128 out.at(i,i) = std::log( out_T(val) );
129 }
130 }
131
132 return true;
133 }
134
135 #if defined(ARMA_OPTIMISE_SYMPD)
136 const bool try_sympd = sympd_helper::guess_sympd_anysize(A);
137 #else
138 const bool try_sympd = false;
139 #endif
140
141 if(try_sympd)
142 {
143 arma_extra_debug_print("op_logmat: attempting sympd optimisation");
144
145 // if matrix A is sympd, all its eigenvalues are positive
146
147 Col<in_T> eigval;
148 Mat<in_T> eigvec;
149
150 const bool eig_status = eig_sym_helper(eigval, eigvec, A, 'd', "logmat()");
151
152 if(eig_status)
153 {
154 // ensure each eigenvalue is > 0
155
156 const uword N = eigval.n_elem;
157 const in_T* eigval_mem = eigval.memptr();
158
159 bool all_pos = true;
160
161 for(uword i=0; i<N; ++i) { all_pos = (eigval_mem[i] <= in_T(0)) ? false : all_pos; }
162
163 if(all_pos)
164 {
165 eigval = log(eigval);
166
167 out = conv_to< Mat<out_T> >::from( eigvec * diagmat(eigval) * eigvec.t() );
168
169 return true;
170 }
171 }
172
173 arma_extra_debug_print("op_logmat: sympd optimisation failed");
174
175 // fallthrough if eigen decomposition failed or an eigenvalue is zero
176 }
177
178
179 Mat<out_T> S(A.n_rows, A.n_cols, arma_nozeros_indicator());
180
181 const in_T* Amem = A.memptr();
182 out_T* Smem = S.memptr();
183
184 const uword n_elem = A.n_elem;
185
186 for(uword i=0; i<n_elem; ++i)
187 {
188 Smem[i] = std::complex<in_T>( Amem[i] );
189 }
190
191 return op_logmat_cx::apply_common(out, S, n_iters);
192 }
193
194
195
196 template<typename T1>
197 inline
198 void
apply(Mat<typename T1::elem_type> & out,const Op<T1,op_logmat_cx> & in)199 op_logmat_cx::apply(Mat<typename T1::elem_type>& out, const Op<T1,op_logmat_cx>& in)
200 {
201 arma_extra_debug_sigprint();
202
203 const bool status = op_logmat_cx::apply_direct(out, in.m, in.aux_uword_a);
204
205 if(status == false)
206 {
207 out.soft_reset();
208 arma_stop_runtime_error("logmat(): transformation failed");
209 }
210 }
211
212
213
214 template<typename T1>
215 inline
216 bool
apply_direct(Mat<typename T1::elem_type> & out,const Op<T1,op_diagmat> & expr,const uword)217 op_logmat_cx::apply_direct(Mat<typename T1::elem_type>& out, const Op<T1,op_diagmat>& expr, const uword)
218 {
219 arma_extra_debug_sigprint();
220
221 typedef typename T1::elem_type eT;
222
223 const diagmat_proxy<T1> P(expr.m);
224
225 bool status = false;
226
227 if(P.is_alias(out))
228 {
229 Mat<eT> tmp;
230
231 status = op_logmat_cx::apply_direct_noalias(tmp, P);
232
233 out.steal_mem(tmp);
234 }
235 else
236 {
237 status = op_logmat_cx::apply_direct_noalias(out, P);
238 }
239
240 return status;
241 }
242
243
244
245 template<typename T1>
246 inline
247 bool
apply_direct_noalias(Mat<typename T1::elem_type> & out,const diagmat_proxy<T1> & P)248 op_logmat_cx::apply_direct_noalias(Mat<typename T1::elem_type>& out, const diagmat_proxy<T1>& P)
249 {
250 arma_extra_debug_sigprint();
251
252 arma_debug_check( (P.n_rows != P.n_cols), "logmat(): given matrix must be square sized" );
253
254 const uword N = P.n_rows;
255
256 out.zeros(N,N);
257
258 for(uword i=0; i<N; ++i)
259 {
260 out.at(i,i) = std::log(P[i]);
261 }
262
263 return true;
264 }
265
266
267
268 template<typename T1>
269 inline
270 bool
apply_direct(Mat<typename T1::elem_type> & out,const Base<typename T1::elem_type,T1> & expr,const uword n_iters)271 op_logmat_cx::apply_direct(Mat<typename T1::elem_type>& out, const Base<typename T1::elem_type,T1>& expr, const uword n_iters)
272 {
273 arma_extra_debug_sigprint();
274
275 typedef typename T1::pod_type T;
276 typedef typename T1::elem_type eT;
277
278 Mat<eT> S = expr.get_ref();
279
280 arma_debug_check( (S.n_rows != S.n_cols), "logmat(): given matrix must be square sized" );
281
282 if(S.n_elem == 0)
283 {
284 out.reset();
285 return true;
286 }
287 else
288 if(S.n_elem == 1)
289 {
290 out.set_size(1,1);
291 out[0] = std::log(S[0]);
292 return true;
293 }
294
295 if(S.is_diagmat())
296 {
297 const uword N = S.n_rows;
298
299 out.zeros(N,N); // aliasing can't happen as S is generated
300
301 for(uword i=0; i<N; ++i) { out.at(i,i) = std::log( S.at(i,i) ); }
302
303 return true;
304 }
305
306 #if defined(ARMA_OPTIMISE_SYMPD)
307 const bool try_sympd = sympd_helper::guess_sympd_anysize(S);
308 #else
309 const bool try_sympd = false;
310 #endif
311
312 if(try_sympd)
313 {
314 arma_extra_debug_print("op_logmat_cx: attempting sympd optimisation");
315
316 // if matrix S is sympd, all its eigenvalues are positive
317
318 Col< T> eigval;
319 Mat<eT> eigvec;
320
321 const bool eig_status = eig_sym_helper(eigval, eigvec, S, 'd', "logmat()");
322
323 if(eig_status)
324 {
325 // ensure each eigenvalue is > 0
326
327 const uword N = eigval.n_elem;
328 const T* eigval_mem = eigval.memptr();
329
330 bool all_pos = true;
331
332 for(uword i=0; i<N; ++i) { all_pos = (eigval_mem[i] <= T(0)) ? false : all_pos; }
333
334 if(all_pos)
335 {
336 eigval = log(eigval);
337
338 out = eigvec * diagmat(eigval) * eigvec.t();
339
340 return true;
341 }
342 }
343
344 arma_extra_debug_print("op_logmat_cx: sympd optimisation failed");
345
346 // fallthrough if eigen decomposition failed or an eigenvalue is zero
347 }
348
349 return op_logmat_cx::apply_common(out, S, n_iters);
350 }
351
352
353
354 template<typename T>
355 inline
356 bool
apply_common(Mat<std::complex<T>> & out,Mat<std::complex<T>> & S,const uword n_iters)357 op_logmat_cx::apply_common(Mat< std::complex<T> >& out, Mat< std::complex<T> >& S, const uword n_iters)
358 {
359 arma_extra_debug_sigprint();
360
361 typedef typename std::complex<T> eT;
362
363 Mat<eT> U;
364
365 const bool schur_ok = auxlib::schur(U,S);
366
367 if(schur_ok == false) { arma_extra_debug_print("logmat(): schur decomposition failed"); return false; }
368
369 //double theta[] = { 1.10e-5, 1.82e-3, 1.62e-2, 5.39e-2, 1.14e-1, 1.87e-1, 2.64e-1 };
370 double theta[] = { 0.0, 0.0, 1.6206284795015624e-2, 5.3873532631381171e-2, 1.1352802267628681e-1, 1.8662860613541288e-1, 2.642960831111435e-1 };
371 // theta[0] and theta[1] not really used
372
373 const uword N = S.n_rows;
374
375 uword p = 0;
376 uword m = 6;
377
378 uword iter = 0;
379
380 while(iter < n_iters)
381 {
382 const T tau = norm( (S - eye< Mat<eT> >(N,N)), 1 );
383
384 if(tau <= theta[6])
385 {
386 p++;
387
388 uword j1 = 0;
389 uword j2 = 0;
390
391 for(uword i=2; i<=6; ++i) { if( tau <= theta[i]) { j1 = i; break; } }
392 for(uword i=2; i<=6; ++i) { if((tau/2.0) <= theta[i]) { j2 = i; break; } }
393
394 // sanity check, for development purposes only
395 arma_debug_check( (j2 > j1), "internal error: op_logmat::apply_direct(): j2 > j1" );
396
397 if( ((j1 - j2) <= 1) || (p == 2) ) { m = j1; break; }
398 }
399
400 const bool sqrtmat_ok = op_sqrtmat_cx::apply_direct(S,S);
401
402 if(sqrtmat_ok == false) { arma_extra_debug_print("logmat(): sqrtmat() failed"); return false; }
403
404 iter++;
405 }
406
407 if(iter >= n_iters) { arma_debug_warn_level(2, "logmat(): reached max iterations without full convergence"); }
408
409 S.diag() -= eT(1);
410
411 if(m >= 1)
412 {
413 const bool helper_ok = op_logmat_cx::helper(S,m);
414
415 if(helper_ok == false) { return false; }
416 }
417
418 out = U * S * U.t();
419
420 out *= eT(eop_aux::pow(double(2), double(iter)));
421
422 return true;
423 }
424
425
426
427 template<typename eT>
428 inline
429 bool
helper(Mat<eT> & A,const uword m)430 op_logmat_cx::helper(Mat<eT>& A, const uword m)
431 {
432 arma_extra_debug_sigprint();
433
434 if(A.is_finite() == false) { return false; }
435
436 const vec indices = regspace<vec>(1,m-1);
437
438 mat tmp(m, m, arma_zeros_indicator());
439
440 tmp.diag(-1) = indices / sqrt(square(2.0*indices) - 1.0);
441 tmp.diag(+1) = indices / sqrt(square(2.0*indices) - 1.0);
442
443 vec eigval;
444 mat eigvec;
445
446 const bool eig_ok = eig_sym_helper(eigval, eigvec, tmp, 'd', "logmat()");
447
448 if(eig_ok == false) { arma_extra_debug_print("logmat(): eig_sym() failed"); return false; }
449
450 const vec nodes = (eigval + 1.0) / 2.0;
451 const vec weights = square(eigvec.row(0).t());
452
453 const uword N = A.n_rows;
454
455 Mat<eT> B(N, N, arma_zeros_indicator());
456
457 Mat<eT> X;
458
459 for(uword i=0; i < m; ++i)
460 {
461 // B += weights(i) * solve( (nodes(i)*A + eye< Mat<eT> >(N,N)), A );
462
463 //const bool solve_ok = solve( X, (nodes(i)*A + eye< Mat<eT> >(N,N)), A, solve_opts::fast );
464 const bool solve_ok = solve( X, trimatu(nodes(i)*A + eye< Mat<eT> >(N,N)), A, solve_opts::no_approx );
465
466 if(solve_ok == false) { arma_extra_debug_print("logmat(): solve() failed"); return false; }
467
468 B += weights(i) * X;
469 }
470
471 A = B;
472
473 return true;
474 }
475
476
477
478 template<typename T1>
479 inline
480 void
apply(Mat<typename T1::elem_type> & out,const Op<T1,op_logmat_sympd> & in)481 op_logmat_sympd::apply(Mat<typename T1::elem_type>& out, const Op<T1,op_logmat_sympd>& in)
482 {
483 arma_extra_debug_sigprint();
484
485 const bool status = op_logmat_sympd::apply_direct(out, in.m);
486
487 if(status == false)
488 {
489 out.soft_reset();
490 arma_stop_runtime_error("logmat_sympd(): transformation failed");
491 }
492 }
493
494
495
496 template<typename T1>
497 inline
498 bool
apply_direct(Mat<typename T1::elem_type> & out,const Base<typename T1::elem_type,T1> & expr)499 op_logmat_sympd::apply_direct(Mat<typename T1::elem_type>& out, const Base<typename T1::elem_type,T1>& expr)
500 {
501 arma_extra_debug_sigprint();
502
503 #if defined(ARMA_USE_LAPACK)
504 {
505 typedef typename T1::pod_type T;
506 typedef typename T1::elem_type eT;
507
508 const unwrap<T1> U(expr.get_ref());
509 const Mat<eT>& X = U.M;
510
511 arma_debug_check( (X.is_square() == false), "logmat_sympd(): given matrix must be square sized" );
512
513 Col< T> eigval;
514 Mat<eT> eigvec;
515
516 const bool status = eig_sym_helper(eigval, eigvec, X, 'd', "logmat_sympd()");
517
518 if(status == false) { return false; }
519
520 const uword N = eigval.n_elem;
521 const T* eigval_mem = eigval.memptr();
522
523 bool all_pos = true;
524
525 for(uword i=0; i<N; ++i) { all_pos = (eigval_mem[i] <= T(0)) ? false : all_pos; }
526
527 if(all_pos == false) { return false; }
528
529 eigval = log(eigval);
530
531 out = eigvec * diagmat(eigval) * eigvec.t();
532
533 return true;
534 }
535 #else
536 {
537 arma_ignore(out);
538 arma_ignore(expr);
539 arma_stop_logic_error("logmat_sympd(): use of LAPACK must be enabled");
540 return false;
541 }
542 #endif
543 }
544
545
546
547 //! @}
548