Nav:  [home][tmp] > [ode]
 

ODE Solver

Die DGL

Doppelpendel-DGL ohne Reibung und ohne Näherungen.
Befestigungspunkt - Stange l1 - Masse m1 - Stange l2 - Masse m2.
Beschreibung durch die beiden Winkel der Stangen (phi, psi), Lagrange-Ansatz, etc.
In den Bildren aufgetragen ist die Lage der ("unteren") Masse m2 in der Ebene; die Schwerkraft wirkt nach unten.
Anfangsbedingung: Alle Simulationen (ausser anders erwähnt) wurden mit den Anfangsbedingungen phi=0.9*Pi, psi=Pi oder mit phi=0.9*Pi, psi=0.5*Pi durchgeführt (welche es sind, kann man unschwer an der Lage des Anfangs der Trajektorie erkennen).

Numerische Verfahren

Mittels adaptivem explizitem Einschrittverfahren der Ordnung 2 (Heun, Collatz) und 4 (Runge-Kutta).
Vergleich der verschiedenen Verfahren bei adaptiver Schrittweitenkontrolle und einem maximalen Diskretisierungsfehler eps=1e-10. (Die Verfahren niedrigerer Ordnung brauchen dazu natürlich kleinere Schrittweiten und dementsprechend länger.)
Man beachte rechts unten: Es handelt sich um ein und das selbe Verfahren aber zwei verschiedenen Implementierungen.

ODE img [10kb] ODE img [9kb]   ODE img [8kb] ODE img [7kb]
ODE img [9kb] ODE img [11kb]   ODE img [9kb] ODE img [14kb]

Einfluss der Fehlerschranke

Vergleich des Runge-Kutta-Verfahrens ("klassisches", also ältestes RK-Verfahren) bei verschiedenen Diskretisierungsfehler-Schranken eps=1e-5 bis eps=1e-13. Wie immer adaptives Verfahren mit gleichen Anfangsbedingungen.

ODE img [8kb] ODE img [10kb]   ODE img [8kb] ODE img [10kb]
ODE img [10kb] ODE img [11kb]   ODE img [10kb] ODE img [11kb]

Auswirkung von Störungen in den Anfangsbedingungen

Die eine Trajektorie benutzt die selben Anfangsbedingungen wie oben, bei der anderen ist der Start-Winkel von m1 von 0.9*Pi auf 0.9*Pi+1e-12 gestört (also eine relative Änderung um etwa 3e-13).
Simulation mit verschiedenen Fehlerschranken eps.

ODE img [11kb] ODE img [10kb]   ODE img [16kb] ODE img [11kb]

Adaptive Schrittweitensteuerung

Rechts:

"Klassisches" Runge-Kutta-Verfahren mit Diskretisierungsfehler eps=1e-9; verwendete Schrittweite für Doppelschritt unten über die Zeit aufgetragen. (Das verwendete adaptive Verfahren berechnet zwei Schritte mit halber Schrittweite (="Doppelschritt") und einen mit voller Schrittweite und schätzt den Diskretisierungsfehler aus der Differenz der Ergebnisse. Ist diese klein genug, so wird das Ergebnis des Doppelschrittes akzeptiert.)

  ODE img [9kb]

Central parts of the code

This code is Copyright (c) 02/2004 by Wolfgang Wieser.
It may be copied under the terms of the GNU GPL.

The complete ODE code is part of AniVision, version 0.3.4a or above.
The graphical test program for the ODE solver routines is available here: odetest.cc.
[Requires QTXlib (bundeled with burn simulation), hlib, Qt and probably Linux and GCC -- and AniVision, of course]

The ODE function:
class MyODEFunc : public NUM::Function_ODE
{
	protected:
		// [overriding virtual]
		void function(double /*x*/,const double *y,double *result)
		{
			register double phi=y[0],phip=y[1],psi=y[2],psip=y[3];
			
			double tmp = m2*l1*l2*sin(phi-psi);
			double a = -tmp*psip*psip - g*(m1+m2)*l1*sin(phi);
			double d =  tmp*phip*phip - g*    m2 *l2*sin(psi);
			double b = l1*l1*(m1+m2);
			double c = m2*l1*l2*cos(phi-psi);
			double e = l2*l2*m2;
			double det = (b*e-c*c);
			
			result[0]=phip;
			result[1]=(a*e-c*d)/det;
			result[2]=psip;
			result[3]=(b*d-c*a)/det;
			
		 #if 0
			// Control: calc energy:
			double T = 0.5*(m1+m2)*SQR(l1*phip) +
				m2*( 0.5*SQR(l2*psip) + l1*l2*phip*psip*cos(phi-psi) );
			double U = -g*( (m1+m2)*l1*cos(phi) + m2*l2*cos(psi) );
			double E = T+U;
			
			static int cnt=0;
			if(!(++cnt%100))
			{  fprintf(stderr,"\n%g",E);  }
		 #endif
		}
	public:
		MyODEFunc() : Function_ODE(4) {}
		~MyODEFunc() {}
};
The solver algorithm (methods below):
void ODESolver::SetAdaptivePars(double epsilon)
{
	// Get order of consistency:
	int p=GetSolverParams()->p;
	
	adapt_limit1 = (1.0-pow(2.0,-p)) * epsilon;
	adapt_limit0 = adapt_limit1 * pow(2.0,p+1);
	// Add some safety margin to prevent up-down-up-down-up - switches.
	// This should normally not be necessary.
	adapt_limit1 *= 0.9;
}


void ODESolver::SingleStep(Function_ODE &odefunc,
	double &xn,double h,double *yn)
{
	ComputeStep(odefunc,xn,h,yn);
	++nsteps;
	xn+=h;
	if(used_h_min>h)  used_h_min=h;
	if(used_h_max<h)  used_h_max=h;
}


void ODESolver::AdaptiveStep(Function_ODE &odefunc,
	double &xn,double &h,double *yn)
{
	int dim=odefunc.Dim();
	double ynF[dim],ynH[dim],ynHH[dim];
	
	double ah=fabs(h);
	for(bool first=1,last=(ah<=h_min);;first=0)
	{
		if(first)
		{
			for(int d=0; d<dim; d++)
			{  ynF[d]=yn[d];  ynH[d]=yn[d];  }
			
			// Do the full step:
			ComputeStep(odefunc,xn,h,ynF);
		}
		else
		{
			// Use saved half step from last time as full step:
			for(int d=0; d<dim; d++)
			{  ynF[d]=ynHH[d];  ynH[d]=yn[d];  }
		}
		
		if(used_h_min>ah)  used_h_min=ah;
		if(used_h_max<ah)  used_h_max=ah;
		
		// Do the two half steps:
		double h2=0.5*h;
		ComputeStep(odefunc,xn,h2,ynH);
		
		// Save that in case we need to reduce the step size:
		if(!last)  for(int d=0; d<dim; d++)  ynHH[d]=ynH[d];
		
		ComputeStep(odefunc,xn+h2,h2,ynH);
		
		// Compute the difference:
		// (maximum norm of full_step minus two_half_steps)
		double delta=0.0;
		for(int d=0; d<dim; d++)
		{
			double diff=fabs(ynF[d]-ynH[d]);
			if(!d || delta<diff)  delta=diff;
		}
		
		if(delta<=adapt_limit0)
		{
			// Accept result.
			xn+=h;
			
			if(delta<=adapt_limit1)
			{
				// Even try larger step size next time:
				h+=h;
				if(h>0.0)
				{  if(h>h_max)  h=h_max;  }
				else
				{  if(-h>h_max)  h=-h_max;  }
			}
			
			break;
		}
		
		// Must reduce step size.
		if(last)
		{  // Already smallest step size.
			xn+=h;
			break;
		}
		
		h=h2;
		if(h>0.0)
		{
			if(h<h_min)
			{  h=h_min;  last=1;  }
		}
		else
		{
			if(-h<h_min)
			{  h=-h_min;  last=1;  }
		}
	}
	
	nsteps+=2;
	for(int d=0; d<dim; d++)
		yn[d]=ynH[d];
}
The different methods:
void ODESolver_Heun::ComputeStep(Function_ODE &odefunc,
	double xn,double h,double *yn)
{
	int dim=odefunc.Dim();
	double k[2][dim];
	double ytmp[dim];
	
	odefunc(xn,yn,k[0]);
	
	for(int d=0; d<dim; d++)
		ytmp[d]=yn[d]+h*k[0][d];
	
	odefunc(xn+h,ytmp,k[1]);
	
	double h2=0.5*h;
	for(int d=0; d<dim; d++)
		yn[d]+=h2*(k[0][d]+k[1][d]);
}

void ODESolver_Collatz::ComputeStep(Function_ODE &odefunc,
	double xn,double h,double *yn)
{
	int dim=odefunc.Dim();
	double k[dim];
	double ytmp[dim];
	
	odefunc(xn,yn,k);
	
	double h2=0.5*h;
	for(int d=0; d<dim; d++)
		ytmp[d]=yn[d]+h2*k[d];
	
	odefunc(xn+h2,ytmp,k);
	
	for(int d=0; d<dim; d++)
		yn[d]+=h*k[d];
}

void ODESolver_RungeKutta::ComputeStep(Function_ODE &odefunc,
	double xn,double h,double *yn)
{
	int dim=odefunc.Dim();
	double k[dim];
	double ydelta[dim];
	double ytmp[dim];
	
	odefunc(xn,yn,k);
	
	double h2=0.5*h;
	for(int d=0; d<dim; d++)
	{
		ydelta[d]=k[d];
		ytmp[d]=yn[d]+h2*k[d];
	}
	
	odefunc(xn+h2,ytmp,k);
	
	for(int d=0; d<dim; d++)
	{
		ydelta[d]+=2.0*k[d];
		ytmp[d]=yn[d]+h2*k[d];
	}
	
	odefunc(xn+h2,ytmp,k);
	
	for(int d=0; d<dim; d++)
	{
		ydelta[d]+=2.0*k[d];
		ytmp[d]=yn[d]+h*k[d];
	}
	
	odefunc(xn+h,ytmp,k);
	
	h2=h/6.0;
	for(int d=0; d<dim; d++)
		yn[d]+=h2*(ydelta[d]+k[d]);
}

void ODESolver_GeneralRungeKutta::ComputeStep(Function_ODE &odefunc,
	double xn,double h,double *yn)
{
	int dim=odefunc.Dim();
	double k[sp.m][dim];
	double ytmp[dim];
	
	// First evaluation (optimized):
	odefunc(xn,yn,k[0]);
	
	// All the other evaluations:
	for(int i=1; i<sp.m; i++)
	{
		for(int d=0; d<dim; d++)
		{
			double tmp=0.0;
			for(int s=0; s<i; s++)
				tmp+=beta(i,s)*k[s][d];
			ytmp[d]=yn[d]+h*tmp;
		}
		
		odefunc(xn+alpha[i]*h,ytmp,k[i]);
	}
	
	// Update values:
	for(int d=0; d<dim; d++)
	{
		double delta=0.0;
		for(int s=0; s<sp.m; s++)
			delta+=gamma[s]*k[s][d];
		yn[d]+=h*delta;
	}
}

[home] [site map] [Impressum] [Datenschutz/privacy policy]
Valid HTML 4.01!
Copyright © 2003-2005 by Wolfgang Wieser
Last modified: 2005-10-25 00:42:57