Discontinuous Galerkin Library
#include "dg/algorithm.h"
adaptive.h
Go to the documentation of this file.
1#pragma once
2
3#include "backend/memory.h"
4#include "ode.h"
5#include "runge_kutta.h"
6
7namespace dg
8{
9
12//MW: The reason these are global lambdas instead of functions is so that make_unique
13//can deduce the correct types when used on dg::AdaptiveTimeloop:
14//std::make_unique<dg::AdaptiveTimeloop<Vec>>( adapt, ode, dg::pid_control, dg::fast_l2norm, 1e-6, 1e-6);
22static auto l2norm = [] ( const auto& x){ return sqrt( dg::blas1::dot(x,x));};
23
33static auto fast_l2norm = []( const auto& x){ return sqrt( dg::blas1::reduce(
34 x, (double)0, dg::Sum(), dg::Square()));};
35
37static auto i_control = []( auto dt, auto eps, unsigned embedded_order, unsigned order)
38{
39 using value_type = std::decay_t<decltype(dt[0])>;
40 return dt[0]*pow( eps[0], -1./(value_type)embedded_order);
41};
43static auto pi_control = []( auto dt, auto eps, unsigned embedded_order, unsigned order)
44{
45 using value_type = std::decay_t<decltype(dt[0])>;
46 if( dt[1] == 0)
47 return i_control( dt, eps, embedded_order, order);
48 value_type m_k1 = -0.8, m_k2 = 0.31;
49 value_type factor = pow( eps[0], m_k1/(value_type)embedded_order)
50 * pow( eps[1], m_k2/(value_type)embedded_order);
51 return dt[0]*factor;
52};
71static auto pid_control = []( auto dt, auto eps, unsigned embedded_order, unsigned order)
72{
73 using value_type = std::decay_t<decltype(dt[0])>;
74 if( dt[1] == 0)
75 return i_control( dt, eps, embedded_order, order);
76 if( dt[2] == 0)
77 return pi_control( dt, eps, embedded_order, order);
78 value_type m_k1 = -0.58, m_k2 = 0.21, m_k3 = -0.1;
79 //value_type m_k1 = -0.37, m_k2 = 0.27, m_k3 = -0.1;
80 value_type factor = pow( eps[0], m_k1/(value_type)embedded_order)
81 * pow( eps[1], m_k2/(value_type)embedded_order)
82 * pow( eps[2], m_k3/(value_type)embedded_order);
83 return dt[0]*factor;
84};
85
87static auto ex_control = [](auto dt, auto eps, unsigned embedded_order, unsigned order)
88{
89 using value_type = std::decay_t<decltype(dt[0])>;
90 if( dt[1] == 0)
91 return i_control( dt, eps, embedded_order, order);
92 value_type m_k1 = -0.367, m_k2 = 0.268;
93 value_type factor = pow( eps[0], m_k1/(value_type)embedded_order)
94 * pow( eps[0]/eps[1], m_k2/(value_type)embedded_order);
95 return dt[0]*factor;
96};
98static auto im_control = []( auto dt, auto eps, unsigned embedded_order, unsigned order)
99{
100 using value_type = std::decay_t<decltype(dt[0])>;
101 if( dt[1] == 0)
102 return i_control( dt, eps, embedded_order, order);
103 value_type m_k1 = -0.98, m_k2 = -0.95;
104 value_type factor = pow( eps[0], m_k1/(value_type)embedded_order)
105 * pow( eps[0]/eps[1], m_k2/(value_type)embedded_order);
106 return dt[0]*dt[0]/dt[1]*factor;
107};
109static auto imex_control = []( auto dt, auto eps, unsigned embedded_order, unsigned order)
110{
111 using value_type = std::decay_t<decltype(dt[0])>;
112 value_type h1 = ex_control( dt, eps, embedded_order, order);
113 value_type h2 = im_control( dt, eps, embedded_order, order);
114 // find value closest to zero
115 return fabs( h1) < fabs( h2) ? h1 : h2;
116};
117
119
121namespace detail{
122template<class value_type>
123struct Tolerance
124{
125 // sqrt(size) is norm( 1)
126 Tolerance( value_type rtol, value_type atol, value_type size) :
127 m_rtol(rtol*sqrt(size)), m_atol( atol*sqrt(size)){}
129 void operator()( value_type u0, value_type& delta) const{
130 delta = delta/ ( m_rtol*fabs(u0) + m_atol);
131 }
132 private:
133 value_type m_rtol, m_atol;
134};
135} //namespace detail
137
159//%%%%%%%%%%%%%%%%%%%Adaptive%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
231template<class Stepper>
233{
234 using stepper_type = Stepper;
235 using container_type = typename Stepper::container_type;
236 using value_type = typename Stepper::value_type;
237 Adaptive() = default;
244 template<class ...StepperParams>
245 Adaptive(StepperParams&& ...ps): m_stepper(std::forward<StepperParams>(ps)...),
246 m_next(m_stepper.copyable()), m_delta(m_stepper.copyable())
247 {
248 dg::blas1::copy( 1., m_next);
249 m_size = dg::blas1::dot( m_next, 1.);
250 }
252 template<class ...Params>
253 void construct(Params&& ...ps)
254 {
255 //construct and swap
256 *this = Adaptive( std::forward<Params>(ps)...);
257 }
258
262 stepper_type& stepper() { return m_stepper;}
264 const stepper_type& stepper() const { return m_stepper;}
265
272 template< class ODE,
273 class ControlFunction = value_type (std::array<value_type,3>,
274 std::array< value_type,3>, unsigned , unsigned),
275 class ErrorNorm = value_type( const container_type&)>
276 void step( ODE&& ode,
277 value_type t0,
278 const container_type& u0,
279 value_type& t1,
280 container_type& u1,
281 value_type& dt,
282 ControlFunction control,
283 ErrorNorm norm,
284 value_type rtol,
285 value_type atol,
286 value_type reject_limit = 2
287 )
288 {
289 // prevent overwriting u0 in case stepper fails
290 m_stepper.step( std::forward<ODE>(ode), t0, u0, m_t_next, m_next, dt,
291 m_delta);
292 m_nsteps++;
293 dg::blas1::subroutine( detail::Tolerance<value_type>( rtol, atol,
294 m_size), u0, m_delta);
295 m_eps0 = norm( m_delta);
296 m_dt0 = dt;
297 if( m_eps0 > reject_limit || std::isnan( m_eps0) )
298 {
299 // if stepper fails, restart controller
300 dt = control( std::array<value_type,3>{m_dt0, 0, m_dt2},
301 std::array<value_type,3>{m_eps0, m_eps1, m_eps2},
302 m_stepper.embedded_order(),
303 m_stepper.order());
304 if( fabs( dt) > 0.9*fabs(m_dt0))
305 dt = 0.9*m_dt0;
306 //0.9*m_dt0 is a safety limit
307 //that prevents an increase of the timestep in case the stepper fails
308 m_failed = true; m_nfailed++;
309 dg::blas1::copy( u0, u1);
310 t1 = t0;
311 return;
312 }
313 if( m_eps0 < 1e-30) // small or zero
314 {
315 dt = 1e14*m_dt0; // a very large number
316 m_eps0 = 1e-30; // prevent storing zero
317 }
318 else
319 {
320 dt = control( std::array<value_type,3>{m_dt0, m_dt1, m_dt2},
321 std::array<value_type,3>{m_eps0, m_eps1, m_eps2},
322 m_stepper.embedded_order(),
323 m_stepper.order());
324 // a safety net
325 if( fabs(dt) > 100*fabs(m_dt0))
326 dt = 100*m_dt0;
327 }
328 m_eps2 = m_eps1;
329 m_eps1 = m_eps0;
330 m_dt2 = m_dt1;
331 m_dt1 = m_dt0;
332 dg::blas1::copy( m_next, u1);
333 t1 = m_t_next;
334 m_failed = false;
335 }
336
338 bool failed() const {
339 return m_failed;
340 }
342 const unsigned& nfailed() const {
343 return m_nfailed;
344 }
346 unsigned& nfailed() {
347 return m_nfailed;
348 }
350 const unsigned& nsteps() const {
351 return m_nsteps;
352 }
354 unsigned& nsteps() {
355 return m_nsteps;
356 }
357
364 const value_type& get_error( ) const{
365 return m_eps0;
366 }
367 private:
368 void reset_history(){
369 m_eps1 = m_eps2 = 1.;
370 m_dt1 = m_dt2 = 0.;
371 }
372 bool m_failed = false;
373 unsigned m_nfailed = 0;
374 unsigned m_nsteps = 0;
375 Stepper m_stepper;
376 container_type m_next, m_delta;
377 value_type m_size, m_eps0 = 1, m_eps1=1, m_eps2=1;
378 value_type m_t_next = 0;
379 value_type m_dt0 = 0., m_dt1 = 0., m_dt2 = 0.;
380};
467{
469 template<class T>
470 bool contains( T& t) const { return true;}
471};
472
475//
498template<class ContainerType>
499struct AdaptiveTimeloop : public aTimeloop<ContainerType>
500{
502 using container_type = ContainerType;
504 AdaptiveTimeloop( ) = default;
505
506
512 AdaptiveTimeloop( std::function<void (value_type, const ContainerType&,
513 value_type&, ContainerType&, value_type&)> step) :
514 m_step(step){
515 m_dt_current = dg::Buffer<value_type>( 0.);
516 }
530 template<class Adaptive, class ODE, class ErrorNorm =
531 value_type( const container_type&), class
532 ControlFunction = value_type(std::array<value_type,3>,
533 std::array<value_type,3>, unsigned, unsigned)>
535 Adaptive&& adapt,
536 ODE&& ode,
537 ControlFunction control,
538 ErrorNorm norm,
539 value_type rtol,
540 value_type atol,
541 value_type reject_limit = 2)
542 {
543 // On the problem of capturing perfect forwarding in a lambda
544 //https://stackoverflow.com/questions/26831382/capturing-perfectly-forwarded-variable-in-lambda
545 //https://vittorioromeo.info/index/blog/capturing_perfectly_forwarded_objects_in_lambdas.html
546
547 m_step = [=, cap = std::tuple<Adaptive, ODE>(std::forward<Adaptive>(adapt),
548 std::forward<ODE>(ode)) ]( auto t0, auto y0, auto& t,
549 auto& y, auto& dt) mutable
550 {
551 std::get<0>(cap).step( std::get<1>(cap), t0, y0, t, y, dt, control, norm,
552 rtol, atol, reject_limit);
553 };
554 m_dt_current = dg::Buffer<value_type>( 0.);
555 }
556
558 template<class ...Params>
559 void construct( Params&& ...ps)
560 {
561 //construct and swap
562 *this = AdaptiveTimeloop( std::forward<Params>( ps)...);
563 }
564
576 m_dt_current = dg::Buffer<value_type>(dt);
577 }
578
603 template< class Domain>
605 value_type t0,
606 const ContainerType& u0,
607 value_type& t1,
608 ContainerType& u1,
609 value_type dt,
610 Domain&& domain,
611 value_type eps_root
612 );
613
614 virtual AdaptiveTimeloop* clone() const{return new
615 AdaptiveTimeloop(*this);}
616 private:
617 virtual void do_integrate(value_type& t0, const container_type& u0,
618 value_type t1, container_type& u1, enum to mode) const;
619 std::function<void( value_type, const ContainerType&, value_type&,
620 ContainerType&, value_type&)> m_step;
621 virtual value_type do_dt( ) const { return m_dt_current.data();}
622 dg::Buffer<value_type> m_dt_current ;
623};
624
626template< class ContainerType>
627void AdaptiveTimeloop<ContainerType>::do_integrate(
628 value_type& t_current,
629 const ContainerType& u0,
630 value_type t1,
631 ContainerType& u1,
632 enum to mode
633 )const
634{
635 value_type deltaT = t1-t_current;
636 bool forward = (deltaT > 0);
637
638 value_type& dt_current = m_dt_current.data();
639 if( dt_current == 0)
640 dt_current = forward ? 1e-6 : -1e-6; // a good a guess as any
641 if( (dt_current < 0 && forward) || ( dt_current > 0 && !forward) )
642 throw dg::Error( dg::Message(_ping_)<<"Error in AdaptiveTimeloop: Timestep has wrong sign! You cannot change direction mid-step: dt "<<dt_current);
643
644 blas1::copy( u0, u1 );
645 while( (forward && t_current < t1) || (!forward && t_current > t1))
646 {
647 if( dg::to::exact == mode
648 &&( (forward && t_current + dt_current > t1)
649 || (!forward && t_current + dt_current < t1) ) )
650 dt_current = t1-t_current;
651 if( dg::to::at_least == mode
652 &&( (forward && dt_current > deltaT)
653 || (!forward && dt_current < deltaT) ) )
654 dt_current = deltaT;
655 // Compute a step and error
656 try{
657 m_step( t_current, u1, t_current, u1, dt_current);
658 }catch ( dg::Error& e)
659 {
660 e.append( dg::Message(_ping_) << "Error in AdaptiveTimeloop::integrate");
661 throw;
662 }
663 if( !std::isfinite(dt_current) || fabs(dt_current) < 1e-9*fabs(deltaT))
664 {
665 value_type dt_current0 = dt_current;
666 dt_current = 0.;
667 throw dg::Error(dg::Message(_ping_)<<"Adaptive integrate failed to converge! dt = "<<std::scientific<<dt_current0);
668 }
669 }
670}
671
672template< class ContainerType>
673template< class Domain>
675 value_type t0,
676 const ContainerType& u0,
677 value_type& t1,
678 ContainerType& u1,
679 value_type dt,
680 Domain&& domain,
681 value_type eps_root
682 )
683{
684 value_type t_current = t0, dt_current = dt;
685 blas1::copy( u0, u1 );
686 ContainerType& current(u1);
687 if( t1 == t0)
688 return;
689 bool forward = (t1 - t0 > 0);
690 if( dt == 0)
691 dt_current = forward ? 1e-6 : -1e-6; // a good a guess as any
692
693 ContainerType last( u0);
694 while( (forward && t_current < t1) || (!forward && t_current > t1))
695 {
696 //remember last step
697 t0 = t_current;
698 dg::blas1::copy( current, last);
699 if( (forward && t_current+dt_current > t1) || (!forward && t_current +
700 dt_current < t1) )
701 dt_current = t1-t_current;
702 // Compute a step and error
703 try{
704 m_step( t_current, current, t_current, current, dt_current);
705 }catch ( dg::Error& e)
706 {
707 e.append( dg::Message(_ping_) << "Error in AdaptiveTimeloop::integrate");
708 throw;
709 }
710 if( !std::isfinite(dt_current) || fabs(dt_current) < 1e-9*fabs(t1-t0))
711 throw dg::Error(dg::Message(_ping_)<<"integrate_in_domain failed to converge! dt = "<<std::scientific<<dt_current);
712 if( !domain.contains( current) )
713 {
714 //start bisection between t0 and t1
715 t1 = t_current;
716
717 // t0 and last are inside
718 dg::blas1::copy( last, current);
719
720 int j_max = 50;
721 for(int j=0; j<j_max; j++)
722 {
723 if( fabs(t1-t0) < eps_root*fabs(t1) + eps_root)
724 {
725 return;
726 }
727 dt_current = (t1-t0)/2.;
728 t_current = t0; //always start integrate from inside
729 value_type failed = t_current;
730 // Naively we would assume that the timestepper always succeeds
731 // because dt_current is always smaller than the previous timestep
732 // However, there are cases when this is not true so we need to
733 // explicitly check!!
734 // t_current = t0, current = last (inside)
735 m_step( t_current, current, t_current, current, dt_current);
736 if( failed == t_current)
737 {
738 dt_current = (t1-t0)/4.;
739 break; // we need to get out of here
740 }
741
742 //stepper( t0, last, t_middle, u1, (t1-t0)/2.);
743 if( domain.contains( current) )
744 {
745 t0 = t_current;
746 dg::blas1::copy( current, last);
747 }
748 else
749 {
750 t1 = t_current;
751 dg::blas1::copy( last, current);
752 }
753 if( (j_max - 1) == j)
754 throw dg::Error( dg::Message(_ping_)<<"integrate_in_domain: too many steps in root finding!");
755 }
756 }
757 }
758}
760
762}//namespace dg
class intended for the use in throw statements
Definition: exceptions.h:83
void append(const Message &message)
Appends a message verbatim to the what string.
Definition: exceptions.h:101
small class holding a stringstream
Definition: exceptions.h:29
#define _ping_
Definition: exceptions.h:12
#define DG_DEVICE
Expands to __host__ __device__ if compiled with nvcc else is empty.
Definition: functions.h:9
void copy(const ContainerTypeIn &source, ContainerTypeOut &target)
Definition: blas1.h:164
OutputType reduce(const ContainerType &x, OutputType zero, BinaryOp binary_op, UnaryOp unary_op=UnaryOp())
Custom (transform) reduction
Definition: blas1.h:139
void subroutine(Subroutine f, ContainerType &&x, ContainerTypes &&... xs)
; Customizable and generic blas1 function
Definition: blas1.h:618
get_value_type< ContainerType1 > dot(const ContainerType1 &x, const ContainerType2 &y)
Binary reproducible Euclidean dot product between two vectors
Definition: blas1.h:87
@ forward
forward derivative (cell to the right and current cell)
Definition: enums.h:98
@ y
y direction
@ x
x direction
Operator< real_type > delta(unsigned n)
Create the unit matrix.
Definition: operator.h:514
static auto ex_control
Definition: adaptive.h:87
static auto imex_control
h_{n+1} = |ex_control| < |im_control| ? ex_control : im_control
Definition: adaptive.h:109
static auto pid_control
Definition: adaptive.h:71
static auto l2norm
Compute using dg::blas1::dot.
Definition: adaptive.h:22
to
Switch for the Timeloop integrate function.
Definition: ode.h:17
static auto i_control
Definition: adaptive.h:37
static auto pi_control
Definition: adaptive.h:43
static auto fast_l2norm
Compute using naive summation.
Definition: adaptive.h:33
static auto im_control
Definition: adaptive.h:98
@ exact
match the ending exactly
@ at_least
integrate to the end or further
typename TensorTraits< std::decay_t< Vector > >::value_type get_value_type
Definition: tensor_traits.h:38
This is the namespace for all functions and classes defined and used by the discontinuous Galerkin li...
An abstract ODE integrator.
Runge-Kutta explicit ODE-integrators.
Driver class for adaptive timestep ODE integration.
Definition: adaptive.h:233
const stepper_type & stepper() const
Read access to internal stepper.
Definition: adaptive.h:264
unsigned & nfailed()
Set total number of failed steps.
Definition: adaptive.h:346
void step(ODE &&ode, value_type t0, const container_type &u0, value_type &t1, container_type &u1, value_type &dt, ControlFunction control, ErrorNorm norm, value_type rtol, value_type atol, value_type reject_limit=2)
Semi-implicit adaptive step.
Definition: adaptive.h:276
stepper_type & stepper()
Allow write access to internal stepper.
Definition: adaptive.h:262
const unsigned & nfailed() const
Get total number of failed steps.
Definition: adaptive.h:342
Stepper stepper_type
Definition: adaptive.h:234
Adaptive()=default
unsigned & nsteps()
Re-set total number of step calls.
Definition: adaptive.h:354
void construct(Params &&...ps)
Perfect forward parameters to one of the constructors.
Definition: adaptive.h:253
typename Stepper::container_type container_type
the type of the vector class in use by Stepper
Definition: adaptive.h:235
typename Stepper::value_type value_type
the value type of the time variable defined by Stepper (float or double)
Definition: adaptive.h:236
const unsigned & nsteps() const
Get total number of step calls.
Definition: adaptive.h:350
const value_type & get_error() const
Get the latest error norm relative to solution vector.
Definition: adaptive.h:364
bool failed() const
Return true if the last stepsize in step was rejected.
Definition: adaptive.h:338
Adaptive(StepperParams &&...ps)
Allocate workspace and construct stepper.
Definition: adaptive.h:245
Integrate using a while loop.
Definition: adaptive.h:500
AdaptiveTimeloop()=default
no allocation
AdaptiveTimeloop(Adaptive &&adapt, ODE &&ode, ControlFunction control, ErrorNorm norm, value_type rtol, value_type atol, value_type reject_limit=2)
Bind the step function of a dg::Adaptive object.
Definition: adaptive.h:534
virtual AdaptiveTimeloop * clone() const
Abstract copy method that returns a copy of *this on the heap.
Definition: adaptive.h:614
dg::get_value_type< ContainerType > value_type
Definition: adaptive.h:501
AdaptiveTimeloop(std::function< void(value_type, const ContainerType &, value_type &, ContainerType &, value_type &)> step)
Construct using a std::function.
Definition: adaptive.h:512
void construct(Params &&...ps)
Perfect forward parameters to one of the constructors.
Definition: adaptive.h:559
void set_dt(value_type dt)
Set initial time-guess in integrate function.
Definition: adaptive.h:575
void integrate_in_domain(value_type t0, const ContainerType &u0, value_type &t1, ContainerType &u1, value_type dt, Domain &&domain, value_type eps_root)
Integrate a differential equation within an integration domain.
ContainerType container_type
Definition: adaptive.h:502
T & data() const
Get write access to the data on the heap.
Definition: memory.h:187
The domain that contains all points.
Definition: adaptive.h:467
bool contains(T &t) const
always true
Definition: adaptive.h:470
Definition: functors.h:116
Definition: subroutines.h:82
Abstract timeloop independent of stepper and ODE.
Definition: ode.h:60