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