←to practical programming

Exercise ODE

  1. (6 points) Embedded Runge-Kutta ODE integrator
    1. Implement an embedded Runge-Kutta stepper rkstepXY (where XY describes the order of the imbedded method used, for example XY=12—"one-two"—for the midpoint-Euler method) of your choice, which advances the solution to the equation

      dy/dx = f(x,y)

      (where y and f are vectors) by a given step h, and estimates the error.

      Something like this,

      public static (vector,vector) rkstep12(
      	Func<double,vector,vector> f /* the f from dy/dx=f(x,y) */
      	double x,   /* the current value of the variable */
      	vector y,   /* the current value y(x) of the sought function */
      	double h    /* the step to be taken */
      ){ // Runge-Kutta Euler/Midpoint method (probably not the most effective)
      	vector k0 = f(x,y); /* embedded lower order formula (Euler) */
      	vector k1 = f(x+h/2,y+k0*(h/2)); /* higher order formula (midpoint) */
      	vector yh = y+k1*h;     /* y(x+h) estimate */
      	vector er = (k1-k0)*h;  /* error estimate */
      	return (yh,er);
      }
      
    2. Implement an adaptive-step-size driver routine wchich advances the solution from a to b (by calling your rkstepXY routine with appropriate step-sizes) keeping the specified relative, eps, and absolute, acc, precision.

      The interface could be like this,

      public static vector driver(
      	Func<double,vector,vector> f /* the f from dy/dx=f(x,y) */
      	double a,                     /* the start-point a */
      	vector ya,                    /* y(a) */
      	double b,                     /* the end-point of the integration */
      	double h=0.01,                  /* initial step-size */
      	double acc=0.01,              /* absolute accuracy goal */
      	double eps=0.01               /* relative accuracy goal */
      ){
      if(a>b) throw new Exception("driver: a>b");
      double x=a; vector y=ya;
      do      {
              if(x>=b) return y; /* job done */
              if(x+h>b) h=b-x;   /* last step should end at b */
              var (yh,erv) = rkstep12(F,x,y,h);
              double tol = Max(acc,yh.norm()*eps) * Sqrt(h/(b-a));
              double err = erv.norm();
              if(err<=tol){ x+=h; y=yh; } // accept step
      	h *= Min( Pow(tol/err,0.25)*0.95 , 2); // reajust stepsize
              }while(true);
      }//driver
      
    3. Debug your routines by solving some interesting systems of ordinary differential equations, for example u''=-u.

    4. Reproduce (with your routines) the example from the scipy.integrate.odeint manual (oscillator with friction).

  2. (3 points) Driver improvements

    • Check the acceptence condition for every component of y and investigate the tolerance/error ratio also for every component (pick the most difficult: the smallest, I suppose). Something like

    tol[i]=Max(acc,Abs(yh[i])*eps)*Sqrt(h/(b-a));
    bool ok=true;
    for(int i=0;i<tol.size;i++) ok = (ok && err[i]<tol[i])
    if(ok){ x+=h; y=yh; }
    double factor = tol[0]/Abs(err[0]);
    for(int i=1;i<tol.size;i++) factor=Min(factor,tol[i]/Abs(err[i]));
    h *= Min( Pow(factor,0.25)*0.95 ,2);
    

    • Make your driver store (optionally) the points {xi, y(xi)} it calculated along the way in two generic lists, say xlist and ylist. Use your own implementation of generic lists ;)

  3. (1 point) Newtonian gravitational three-body problem
    • The dynamics of the Newtonian gravitational three-body problem is generally chaotic. However, there apparently exists a remarkable stable planar periodic solution in the shape of figure-8 [Wikipedia: Special-case solutions; Alain Chenciner, Richard Montgomeryi, arXiv:math/0011268].
    • Using your numerical ODE integrator reproduce this solution.
    • Hints: the Newtonian equations of motion for several gravitating bodies with masses mi is given as
    midvi/dt = ∑j≠i ( Gmimj /|rj-ri|2 ) ( rj-ri /|rj-ri| )