Extension: Matrix functions
#include "dg/matrix/matrix.h"
Loading...
Searching...
No Matches
contours.h
Go to the documentation of this file.
1#pragma once
2#include "dg/algorithm.h"
3#include "optimise.h"
4
10namespace dg{
11namespace mat{
12
14//The following is an implementation of the python notebook
15
16double f_alpha( double alphabar, double lambda = 1.)
17{
18 return 1-exp( -lambda*alphabar);
19}
20double finv_alpha( double alpha, double lambda = 1.)
21{
22 return - log( 1. - alpha)/lambda;
23}
24double df_alpha( double alphabar, double lambda = 1)
25{
26 return lambda*exp( -alphabar*lambda);
27}
28double ddf_alpha( double alphabar, double lambda = 1)
29{
30 return -lambda*lambda*exp( -alphabar*lambda);
31}
32
33// Generator of a Talbot curve with N/2 nodes (becuase only one Quadrant is returned due to symmetry)
34// There are 4 real params and there will be N/2 nodes
35// !! BEWARE: The original Talbot curve surrounds the **negative** real axis
36// counterclockwise in the second Quadrant starting at 0. Since we only deal with **positive** (semi)-definite
37// matrices that have positive Eigenvalues >= 0 and only positive gyro-radii rho we mirror
38// the curve to the fourth quadrant!!
39std::pair<std::vector<thrust::complex<double>>,std::vector<thrust::complex<double>>>
40 weights_and_nodes_talbot( unsigned N, const std::vector<double>& params)
41{
42 thrust::complex<double> I( 0,1);
43 double h = M_PI/(double)N;
44 unsigned n = N/2;
45 std::vector<thrust::complex<double>> zk(N/2);
46 std::vector<thrust::complex<double>> wk(N/2);
47 double mu = params[0];
48 double sigma = params[1];
49 double nu = params[2];
50 double alphabar = params[3];
51 double alpha = f_alpha( alphabar);
52
53 for( unsigned k=0; k<n; k++)
54 {
55 double x = 2.0*h*k + h;
56 zk[k] = -(double)N*(-sigma + mu*x/tan(alpha*x) + nu*I*x);
57 wk[k] = I*(mu/tan(alpha*x) - mu*x*alpha/sin(alpha*x)/sin(alpha*x) + nu*I);
58 }
59 return std::make_pair( zk, wk);
60}
61
62std::pair<std::vector<thrust::complex<double>>,std::vector<thrust::complex<double>>>
63 jacobian_talbot( unsigned N, const std::vector<double>& params)
64{
65 thrust::complex<double> I( 0,1);
66 double h = M_PI/(double)N;
67 unsigned n = N/2;
68 std::vector<thrust::complex<double>> dzk(4*n);
69 std::vector<thrust::complex<double>> dwk(4*n);
70 double mu = params[0];
71 double alphabar = params[3];
72 double alpha = f_alpha( alphabar);
73 double dalpha = df_alpha( alphabar);
74 for( unsigned k=0; k<n; k++)
75 {
76 double Nc = (double)N;
77 double x = 2.0*h*k + h;
78 dzk[0*n+k] = -Nc*( x/tan(alpha*x));
79 dzk[1*n+k] = -Nc*(-1. );
80 dzk[2*n+k] = -Nc*(I*x);
81 dzk[3*n+k] = -Nc*(-mu*x*x/sin(alpha*x)/sin(alpha*x));
82 dzk[3*n+k] *= dalpha;
83 dwk[0*n+k] = I*(1./tan(alpha*x) - x*alpha/sin(alpha*x)/sin(alpha*x) );
84 dwk[1*n+k] = -0;
85 dwk[2*n+k] = -1;
86 dwk[3*n+k] = I*(x*alpha/tan(alpha*x)-1.)*2*mu*x/sin(alpha*x)/sin(alpha*x);
87 dwk[3*n+k] *= dalpha;
88
89 }
90 return std::make_pair( dzk, dwk);
91}
92
93std::vector<double> weights_and_nodes2params( const
94std::pair<std::vector<thrust::complex<double>>,std::vector<thrust::complex<double>>>& zkwk)
95{
96 const auto& zk = zkwk.first;
97 const auto& wk = zkwk.second;
98 std::vector<double> params( 4*zk.size());
99 unsigned n = zk.size();
100 for( unsigned i=0; i<n; i++)
101 {
102 params[0*n+i] = zk[i].real();
103 params[1*n+i] = zk[i].imag();
104 }
105 for( unsigned i=0; i<n; i++)
106 {
107 params[2*n+i] = wk[i].real();
108 params[3*n+i] = wk[i].imag();
109 }
110 return params;
111}
112
113
114// There are 2*N real params and there will be N/2 nodes
115std::pair<std::vector<thrust::complex<double>>,std::vector<thrust::complex<double>>>
116 weights_and_nodes_identity( unsigned N, const std::vector<double>& params )
117{
118 if( N != params.size()/2)
119 throw dg::Error(dg::Message(_ping_)<<"N "<<N<<" must match 0.5 params.size "<<params.size()/2<<"!");
120 unsigned n = N/2;
121 std::vector<thrust::complex<double>> zk(n);
122 std::vector<thrust::complex<double>> wk(n);
123 for( unsigned i=0; i<n; i++)
124 zk[i] = thrust::complex<double>(params[0*n+i], params[1*n+i]);
125 for( unsigned i=0; i<n; i++)
126 wk[i] = thrust::complex<double>(params[2*n+i], params[3*n+i]);
127 return std::make_pair( zk, wk);
128}
129std::pair<std::vector<thrust::complex<double>>,std::vector<thrust::complex<double>>>
130 jacobian_identity( unsigned N, const std::vector<double>&)
131{
132 unsigned n = N/2;
133 std::vector<thrust::complex<double>> dzk(4*n*n, {0.});
134 std::vector<thrust::complex<double>> dwk(4*n*n, {0.});
135 for( unsigned k=0; k<n; k++)
136 {
137 dzk[(0*n+k)*n + k] = thrust::complex<double>(1,0);
138 dzk[(1*n+k)*n + k] = thrust::complex<double>(0,1);
139 dwk[(2*n+k)*n + k] = thrust::complex<double>(1,0);
140 dwk[(3*n+k)*n + k] = thrust::complex<double>(0,1);
141 }
142 return std::make_pair( dzk, dwk);
143}
144
145
147
148// target_result
149// target_error
150//
164struct LeastSquaresCauchyError
165{
177 template<class Generator, class UnaryFunction>
178 LeastSquaresCauchyError( unsigned N, Generator generate, UnaryFunction func, const
179 std::vector<double>& rrs, const std::vector<double>& lls, unsigned order = 2):
180 m_N(N), m_func( func), m_generate(generate),
181 m_rrs(rrs), m_lls(lls), m_exact( lls.size()*rrs.size()),
182 m_func_rrs( N/2*rrs.size()), m_order(order)
183 {
184 m_nl= lls.size();
185 m_nr= rrs.size();
186 for( unsigned i=0; i<m_nl; i++)
187 for( unsigned j=0; j<m_nr; j++)
188 m_exact[i*m_nr+j] = (func( thrust::complex<double>(m_lls[i]*m_rrs[j]))).real();
189 }
190 void result( const std::vector<double>& params, std::vector<double>& result)
191 {
192 // @note The Talbot curve assumes all Eigenvalues lie on the **negative** real axis
193 // so we need to take the negative nodes and weights if our Eigenvalues are all > 0
194 auto pair = m_generate( m_N, params);
195 const auto& zk = pair.first;
196 const auto& wk = pair.second;
197 unsigned n = zk.size();
198 dg::blas1::copy( 0, result);
199 for( unsigned k=0; k<n; k++)
200 for( unsigned j=0; j<m_nr; j++)
201 m_func_rrs[k*m_nr + j] = wk[k]*m_func( m_rrs[j]*zk[k]);
202 for( int k=n-1; k>=0; k--)
203 for( unsigned i=0; i<m_nl; i++)
204 for( unsigned j=0; j<m_nr; j++)
205 result[i*m_nr+j]+= 2*(m_func_rrs[k*m_nr+j]/(zk[k]-m_lls[i])).real();
206 }
207 void error( const std::vector<double>& params, std::vector<double>& err)
208 {
209 result( params, err);
210 dg::blas1::axpby( -1., m_exact, 1., err);
211 }
212 void set_order ( unsigned order){
213 m_order = order;
214 }
215
216 void operator()( const std::vector<double>& params, std::vector<double>& res)
217 {
218 //dg::Timer t;
219 //t.tic();
220 result( params, res);
221 //t.toc();
222 //std::cout << "Computing result "<<t.diff()<<"\n";
223 //t.tic();
224 dg::blas1::axpby( -1., m_exact, 1., res);
225 if ( m_order == 2)
226 dg::blas1::pointwiseDot( res, res, res); // least squares converge better in r^4
227
228 //t.toc();
229 //std::cout << "Computing error "<<t.diff()<<"\n";
230 }
231 private:
232 unsigned m_N, m_nl, m_nr;
233 std::function<thrust::complex<double>(thrust::complex<double>)> m_func;
234 std::function<std::pair<std::vector<thrust::complex<double>>, std::vector<thrust::complex<double>>>(unsigned, const std::vector<double>&)> m_generate;
235 std::vector<double> m_rrs, m_lls, m_exact;
236 std::vector<thrust::complex<double>> m_func_rrs;
237 unsigned m_order = 2;
238};
239
240// target_jacobian
241struct LeastSquaresCauchyJacobian
242{
247 template<class Generator, class GeneratorJac, class UnaryFunction, class UnaryFunctionD>
248 LeastSquaresCauchyJacobian( unsigned N, Generator generate, GeneratorJac generateJac,
249 UnaryFunction func, UnaryFunctionD dxfunc, const std::vector<double>& rrs, const std::vector<double>& lls, unsigned order = 2):
250 m_N(N), m_func( func), m_dxfunc(dxfunc),
251 m_generate(generate), m_generateJac( generateJac),
252 m_rrs(rrs), m_lls(lls), m_exact( lls.size()*rrs.size()),
253 m_result( m_exact),
254 m_func_rrs(N/2*rrs.size()), m_order ( order)
255 {
256 m_nl= lls.size();
257 m_nr= rrs.size();
258 for( unsigned i=0; i<m_nl; i++)
259 for( unsigned j=0; j<m_nr; j++)
260 m_exact[i*m_nr+j] = (func( thrust::complex<double>(m_lls[i]*m_rrs[j]))).real();
261 }
262
263 void set_order( unsigned order) { m_order = order;}
264 void operator()( const std::vector<double>& params, std::vector<std::vector<double>>& jac)
265 {
266 //dg::Timer t;
267 //t.tic();
268 auto pair = m_generate( m_N, params);
269 const auto& zk = pair.first;
270 const auto& wk = pair.second;
271 auto Jacpair = m_generateJac( m_N, params);
272 const auto& dzk = Jacpair.first;
273 const auto& dwk = Jacpair.second;
274 unsigned n = zk.size();
275 //t.toc();
276 //std::cout <<"Generating pairs took "<<t.diff()<<"\n";
277 //t.tic();
278 dg::blas1::copy( 0, m_result);
279 for( unsigned k=0; k<n; k++)
280 for( unsigned j=0; j<m_nr; j++)
281 m_func_rrs[k*m_nr+j] = wk[k]*m_func( m_rrs[j]*zk[k]);
282 for( int k=n-1; k>=0; k--)
283 for( unsigned i=0; i<m_nl; i++)
284 for( unsigned j=0; j<m_nr; j++)
285 m_result[i*m_nr+j]+= 2*(m_func_rrs[k*m_nr+j]/(zk[k] -m_lls[i] )).real();
286 dg::blas1::axpby( -1., m_exact, 1., m_result);
287 //t.toc();
288 //std::cout <<"Computing result "<<t.diff()<<"\n";
289 //t.tic();
290 std::vector<thrust::complex<double>> tmp( m_func_rrs.size());
291 dg::blas1::copy( 0, jac);
292 for( unsigned p=0; p<params.size(); p++)
293 {
294 for( unsigned k=0; k<n; k++)
295 for( unsigned j=0; j<m_nr; j++)
296 tmp[k*m_nr+j] = wk[k]*m_rrs[j]*dzk[p*n+k]*m_dxfunc(m_rrs[j]*zk[k]);
297 for( int k=n-1; k>=0; k--)
298 for( unsigned i=0; i<m_nl; i++)
299 for( unsigned j=0; j<m_nr; j++)
300 jac[p][i*m_nr+j]+= 2.*((m_func_rrs[k*m_nr+j]*(
301 dwk[p*n+k]/wk[k]-dzk[p*n+k]/(zk[k]-m_lls[i])) +
302 tmp[k*m_nr+j])/(zk[k] - m_lls[i])).real();
303 if( m_order == 2)
304 dg::blas1::pointwiseDot( 2., jac[p], m_result, 0., jac[p]);
305 }
306 //t.toc();
307 //std::cout <<"Computing jacobian "<<t.diff()<<"\n";
308 }
309 private:
310 unsigned m_N, m_nl, m_nr;
311 std::function<thrust::complex<double>(thrust::complex<double>)> m_func, m_dxfunc;
312 std::function<std::pair<std::vector<thrust::complex<double>>, std::vector<thrust::complex<double>>>(unsigned, const std::vector<double>&)> m_generate, m_generateJac;
313 std::vector<double> m_rrs, m_lls, m_exact, m_result;
314 std::vector<thrust::complex<double>> m_func_rrs;
315 unsigned m_order = 2;
316};
317
318std::vector<double> generate_range( double min, double max, unsigned per_order = 20, bool with_zero = false)
319{
320 unsigned orders = unsigned (log10(max) - log10(min));
321 if ( orders == 0)
322 orders = 1;
323 std::vector<double> range(orders*per_order);
324 unsigned N = range.size();
325 double h = (log10(max) - log10(min))/double(N-1);
326 for( unsigned i=0; i<N; i++)
327 range[i] = pow( 10.0, log10(min) + i*h);
328 if( with_zero)
329 range.insert( range.begin(), 0);
330 return range;
331}
333
334// Not such a great idea:
335template<class ContainerType, class UnaryFunc>
336void result_talbot( unsigned N, double rrs, double lls,
337 const std::vector<ContainerType>& ps,
338 UnaryFunc func,
339 ContainerType& result)
340{
341 dg::blas1::subroutine( [N,rrs,lls,func]DG_DEVICE(
342 double mu, double sigma, double nu, double alphabar, double& result)
343 {
344 thrust::complex<double> I( 0,1);
345 double h = M_PI/(double)N;
346 unsigned n = N/2;
347 result = 0;
348 double alpha = 1.-exp(-alphabar);
349 for( int k=n-1; k>=0; k--)
350 {
351 double x = 2*h*k + h;
352 double tanx = tan(alpha*x);
353 double sinx = sin(alpha*x);
354 thrust::complex<double> zk( -(double)N*(-sigma + mu*x/tanx), -nu*x*N);
355 thrust::complex<double> wk(-nu, (mu/tanx - mu*x*alpha/sinx/sinx));
356 result+= 2*(wk*func(rrs*zk)/(zk-lls)).real();
357 }
358 }, ps[0], ps[1], ps[2], ps[3], result);
359}
360
361template<class ContainerType, class UnaryFunc>
362void error_talbot( unsigned N, const std::vector<double>& rrs, const std::vector<double>& lls,
363 const std::vector<ContainerType>& ps,
364 UnaryFunc f,
365 ContainerType& error)
366{
367 dg::blas1::copy( 0, error);
368 ContainerType tmp( error);
369 for( unsigned i=0; i<lls.size();i++)
370 for( unsigned j=0; j<rrs.size();j++)
371 {
372 result_talbot( N, rrs[j], lls[i], ps, f, tmp);
373 dg::blas1::plus( tmp, -f(thrust::complex<double>(lls[i]*rrs[j])).real());
374 dg::blas1::pointwiseDot( 1., tmp, tmp, 1., error);
375 }
376}
378
380
381struct CauchyOptimizer
382{
383 CauchyOptimizer( )
384 {
385 re_init();
386 }
387
388
389 void set_verbose( bool verbose) { m_verbose = verbose;}
390
412 template<class UnaryFunc, class UnaryFuncD>
413 const std::pair<std::vector<thrust::complex<double>>, std::vector<thrust::complex<double>>>& update_zkwk(
414 bool& changed,
415 UnaryFunc func, UnaryFuncD dxfunc, double lmin, double lmax,
416 double dmin, double dmax, bool with_zero, double eps = 1e-4)
417 {
418#ifdef MPI_VERSION
419 int rank;
420 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
421#endif //MPI
422 //double error_tolerance_factor = 2.718; // == e
423 double error_tolerance_factor = 10;
424 // We assume that our optimal curve converges with eps \propto exp( -n
425 // ) and thus an increase or decrease by one node decreases/increases
426 // the error by e. However, there is a danger that in corner cases the
427 // error interval [eps/e ; eps] cannot be reached so we increase the
428 // tolerance to a safer 10!
429 unsigned max_nodes = 36; // exp( - 36 ) Approx 1e-16
430 const unsigned order = 2;
431
432 // 1. Generate range
433 auto rrs = dg::mat::generate_range( dmin, dmax, 20);
434 auto lls = dg::mat::generate_range( lmin, lmax, 20, with_zero );
435 std::vector<double> results( lls.size()*rrs.size());
436 // 2. Test if currently used nodes are enough
437 unsigned n = m_zkwk.first.size();
438 dg::mat::LeastSquaresCauchyError
439 Icauchy( 2*n, dg::mat::weights_and_nodes_identity, func, rrs, lls, order);
440 Icauchy.error( m_paramsI, results);
441 double current_eps = dg::blas1::reduce( results, -1e300, thrust::maximum<double>(), dg::ABS<double>() );
442 if( current_eps > 100*eps || current_eps < eps/error_tolerance_factor) // far out of reach
443 re_init(); // re-init
444 changed = false;
445 while( current_eps > eps && n < max_nodes )
446 {
447 // First optimize the Talbot curve
448 dg::mat::LeastSquaresCauchyError
449 cauchy( 2*n, dg::mat::weights_and_nodes_talbot, func, rrs, lls, order);
450 dg::mat::LeastSquaresCauchyJacobian
451 jac( 2*n, dg::mat::weights_and_nodes_talbot, dg::mat::jacobian_talbot, func, dxfunc, rrs, lls, order);
452 // One can play between 1 and 2 here (2 seems to be slightly better)
453
454 unsigned steps = levenberg_marquardt( cauchy, jac, m_params, results, 1e-5, 1000);
455 if( m_verbose )
456 {
457 DG_RANK0 std::cout << "# Current n "<<n<<"\n";
458 DG_RANK0 std::cout << "# Num steps in Levenberg Marquardt "<<steps<<"\n";
459 cauchy.error( m_params, results);
460 double cauchy_error = dg::blas1::dot( results, results);
461 DG_RANK0 std::cout << "# Talbot error "<<cauchy_error<<" ";
462 DG_RANK0 std::cout << "# with params "<<m_params[0]<<" "<<m_params[1]<<" "<<m_params[2]<<" "<<m_params[3]<<"\n";
463 double abs_max = dg::blas1::reduce( results, -1e300, thrust::maximum<double>(), dg::ABS<double>());
464 DG_RANK0 std::cout << "# Abs max error "<< abs_max<<"\n";
465 }
466
467 // Second: from there find better values
468 dg::mat::LeastSquaresCauchyError
469 Icauchy( 2*n, dg::mat::weights_and_nodes_identity, func, rrs, lls, order);
470 dg::mat::LeastSquaresCauchyJacobian
471 Ijac( 2*n, dg::mat::weights_and_nodes_identity, dg::mat::jacobian_identity, func, dxfunc, rrs, lls, order);
472 // convert params to paramsI
473 auto zkwk = dg::mat::weights_and_nodes_talbot( 2*n, m_params);
474 m_paramsI = dg::mat::weights_and_nodes2params( zkwk);
475 unsigned stepsI = levenberg_marquardt( Icauchy, Ijac, m_paramsI, results, 1e-5, 1000);
476 Icauchy.error( m_paramsI, results);
477 current_eps = dg::blas1::reduce( results, -1e300, thrust::maximum<double>(), dg::ABS<double>() );
478 if( m_verbose)
479 {
480 double cauchy_error = dg::blas1::dot( results, results);
481 DG_RANK0 std::cout << "# Num steps in Levenberg Marquardt Id "<<stepsI<<"\n";
482 DG_RANK0 std::cout << "# Cauchy I error "<<cauchy_error<<"\n";
483 DG_RANK0 std::cout << "# Abs max I error "<<current_eps<<"\n";
484 }
485 m_zkwk = dg::mat::weights_and_nodes_identity( 2*n, m_paramsI);
486 changed = true;
487 n++;
488 }
489 if( n >= max_nodes)
490 {
491 re_init();
492 throw dg::Error( dg::Message(_ping_)<<"Error! Maximum number of nodes (36) reached! Tolerance "<<eps<<" cannot be reached! Current eps "<<current_eps);
493 }
494 m_called_previously = true;
495 return m_zkwk;
496 }
497 unsigned num_nodes() const {return m_zkwk.first.size();}
498 private:
499 void re_init(){
500 m_called_previously = false;
501 // init zk and wk with default Talbot parameters
502 m_params = {0.5017,0.6122,0.2645,dg::mat::finv_alpha(0.6407)};
503 m_zkwk = dg::mat::weights_and_nodes_talbot( 2*2, m_params);
504 m_paramsI = dg::mat::weights_and_nodes2params( m_zkwk);
505 }
506
507 bool m_called_previously = false;
508 std::vector<double> m_params, m_paramsI;
509 std::pair<std::vector<thrust::complex<double>>, std::vector<thrust::complex<double>>> m_zkwk; // zkwk == paramsI
510 bool m_verbose = false;
511};
512
514
515} //namespace mat
516} //namespace dg
517
#define _ping_
void copy(const ContainerTypeIn &source, ContainerTypeOut &target)
void axpby(value_type alpha, const ContainerType1 &x, value_type1 beta, ContainerType &y)
void pointwiseDot(value_type alpha, const ContainerType1 &x1, const ContainerType2 &x2, value_type1 beta, ContainerType &y)
void plus(ContainerType &x, value_type alpha)
OutputType reduce(const ContainerType &x, OutputType zero, BinaryOp binary_op, UnaryOp unary_op=UnaryOp())
void subroutine(Subroutine f, ContainerType &&x, ContainerTypes &&... xs)
auto dot(const ContainerType1 &x, const ContainerType2 &y)
unsigned levenberg_marquardt(Func fun, Jacobian jac, ContainerType0 &x0, const ContainerType1 &copyable, double tol=1e-8, unsigned max_iter=1000)
The Levenberg Marquardt algorithm.
Definition optimise.h:316
#define DG_DEVICE
const double alpha
Definition lanczos_b.cpp:11
const double n
Definition lanczos_b.cpp:13
Functions for optimizing Contours.
#define M_PI
M_PI is non-standard ... so MSVC complains.
Definition sqrt_cauchy.h:10
double mu(double s, unsigned i, unsigned n)
Definition tridiaginv_b.cpp:11
#define DG_RANK0