Applicazione del metodo Runge-Kutta a ODE di secondo ordine


11

Come posso sostituire il metodo Euler con il Runge-Kutta del 4 ° ordine per determinare il movimento di caduta libera in magnitudo gravitazionale non costante (es. Caduta libera da 10 000 km dal suolo)?

Finora ho scritto una semplice integrazione con il metodo Euler:

while()
{
    v += getMagnitude(x) * dt;
    x += v * dt;
    time += dt;
}

x variabile indica la posizione corrente, v significa velocità, getMagnitude (x) restituisce l'accelerazione sulla posizione x.

Ho provato a implementare RK4:

while()
{
    v += rk4(x, dt) * dt; // rk4() instead of getMagintude()
    x += v * dt;
    time += dt;
}

dove il corpo della funzione rk4 () è:

inline double rk4(double tx, double tdt)
{
   double k1 = getMagnitude(tx);
   double k2 = getMagnitude(tx + 0.5 * tdt * k1);
   double k3 = getMagnitude(tx + 0.5 * tdt * k2);
   double k4 = getMagnitude(tx + tdt * k3);

   return (k1 + 2*k2 + 2*k3 + k4)/6.0;
}

Ma qualcosa non va, perché mi sto integrando solo una volta usando RK4 (accelerazione). L'integrazione della velocità con RK4 non ha senso perché è uguale a v * dt.

Potresti dirmi come risolvere le equazioni differenziali del secondo ordine usando l'integrazione di Runge-Kutta? Devo implementare RK4 calcolando i coefficienti k1, l1, k2, l2 ... l4? Come lo posso fare?


Ciao @Marcin, ho modificato il tuo titolo per riflettere meglio quello che penso sia il tuo problema. Penso che potremmo ottenere risposte più utili e sarà più ricercabile per gli altri che vedranno questa domanda in futuro con il nuovo titolo. Sentiti libero di cambiarlo se non sei d'accordo.
Doug Lipinski

Risposte:


17

Sembra esserci un po 'di confusione su come applicare metodi multi-step (ad es. Runge-Kutta) a ODE o sistemi di ODE di secondo ordine o superiori. Il processo è molto semplice una volta capito, ma forse non ovvio senza una buona spiegazione. Il seguente metodo è quello che trovo più semplice.

Nel tuo caso, l'equazione differenziale che vorresti risolvere è . Il primo passo è scrivere questo ODE del secondo ordine come un sistema di ODE del primo ordine. Questo è fatto comeF=mx¨

[x˙v˙]=[vF/m]

Tutte le equazioni di questo sistema devono essere risolti contemporaneamente, vale a dire che si dovrebbe non anticipo e poi anticipo , dovrebbero essere entrambi avanzato allo stesso tempo. Nelle lingue che supportano le operazioni vettoriali senza loop, questo è fatto facilmente facendo tutti i termini necessari nei vettori di codice di lunghezza 2. La funzione che calcola il lato destro (il tasso di cambiamento) del tuo ODE dovrebbe restituire un vettore di lunghezza 2 , a dovrebbe essere vettori di lunghezza 2, e la vostra variabile di stato dovrebbe essere un vettore di lunghezza 2. In MATLAB il codice necessario per il tempo di fare un passo può essere scritta come:vxk1k4(x,v)

while (t<TMAX)
    k1 = RHS( t, X );
    k2 = RHS( t + dt / 2, X + dt / 2 * k1 );
    k3 = RHS( t + dt / 2, X + dt / 2 * k2 );
    k4 = RHS( t + dt, X + dt * k3 );
    X = X + dt / 6 * ( k1 + 2 * k2 + 2 * k3 + k4 );
    t = t + dt;
end

dove e restituisce un vettore contenente . Come puoi vedere, vettorializzando cose che non hai nemmeno bisogno di cambiare la sintassi del codice RK4, indipendentemente da quante equazioni ci siano nel tuo sistema ODE.X=(x,v)RHS( t, X )(x˙(t),v˙(t))

Sfortunatamente C ++ non supporta nativamente operazioni vettoriali come questa, quindi è necessario utilizzare una libreria vettoriale, utilizzare loop o scrivere manualmente le parti separate. In C ++ puoi usare std::valarrayper ottenere lo stesso effetto. Ecco un semplice esempio funzionante con accelerazione costante.

#include <valarray>
#include <iostream>

const size_t NDIM = 2;

typedef std::valarray<double> Vector;

Vector RHS( const double t, const Vector X )
{
  // Right hand side of the ODE to solve, in this case:
  // d/dt(x) = v;
  // d/dt(v) = 1;
  Vector output(NDIM);
  output[0] = X[1];
  output[1] = 1;
  return output;
}

int main()
{

  //initialize values

  // State variable X is [position, velocity]
  double init[] = { 0., 0. };
  Vector X( init, NDIM );

  double t = 0.;
  double tMax=5.;
  double dt = 0.1;

  //time loop
  int nSteps = round( ( tMax - t ) / dt );
  for (int stepNumber = 1; stepNumber<=nSteps; ++stepNumber)
  {

    Vector k1 = RHS( t, X );
    Vector k2 = RHS( t + dt / 2.0,  X + dt / 2.0 * k1 );
    Vector k3 = RHS( t + dt / 2.0, X + dt / 2.0 * k2 );
    Vector k4 = RHS( t + dt, X + dt * k3 );

    X += dt / 6.0 * ( k1 + 2.0 * k2 + 2.0 * k3 + k4 );
    t += dt;
  }
  std::cout<<"Final time: "<<t<<std::endl;
  std::cout<<"Final position: "<<X[0]<<std::endl;
  std::cout<<"Final velocity: "<<X[1]<<std::endl;

}

6
" Sfortunatamente il C ++ non supporta nativamente operazioni vettoriali come questa " Penso che lo faccia, anche nella libreria standard, ma non necessariamente facile da usare con altre librerie di algebra lineare: en.cppreference.com/w/cpp/numeric/valarray penso anche le librerie di algebra lineare comune come Eigen, dovrebbero essere considerate "supporto".
Kirill

1
@Kirill, grazie per il suggerimento. Sono ancora relativamente nuovo in C ++ e non ho mai usato valarray prima, ho anche imparato qualcosa di utile! Modifica da aggiungere.
Doug Lipinski,

1
Forse anche questo consiglio sarà utile: 1) Usa il formato clang per formattare automaticamente il tuo codice, è davvero standard e uniforme. 2) Utilizzare typedef std::valarray<double> Vectorper tipi comunemente usati. 3) Utilizzare const int NDIM = 2invece di #defineper sicurezza e correttezza del tipo. 4) Da C ++ 11 è possibile sostituire il corpo di RHS semplicemente con return {X[1], 1}. 5) È davvero insolito in C ++ (diversamente da C) dichiarare prima le variabili, quindi inizializzarle in seguito, preferire dichiarare le variabili nello stesso punto in cui le inizializzate ( double t = 0., ecc.)
Kirill

1
@MarcinW. RHS()calcola il lato destro dell'equazione differenziale. Il vettore di stato X è (x, v), quindi dX / dt = (dx / dt, dv / dt) = (v, a). Per il tuo problema (se a = G * M / x ^ 2) RHS dovrebbe tornare { X[1], G*M/(X[0]*X[0]) }.
Doug Lipinski il

1
@Kirill Lo so, ma funziona solo dal C ++ 11, il che significa che non funziona con le opzioni predefinite del compilatore sui compilatori più popolari. Ho scelto di lasciarlo fuori a favore di qualcosa che funzioni anche con i vecchi standard e, si spera, ridurre la confusione causata dall'impossibilità di compilare il codice.
Doug Lipinski
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.