Áp dụng phương pháp Runge-Kutta cho ODE bậc hai


11

Làm cách nào tôi có thể thay thế phương pháp Euler bằng Runge-Kutta theo thứ tự thứ 4 để xác định chuyển động rơi tự do ở cường độ trọng lực không đổi (ví dụ: rơi tự do từ 10 000 km so với mặt đất)?

Cho đến nay tôi đã viết tích hợp đơn giản bằng phương pháp Euler:

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

biến x có nghĩa là vị trí hiện tại, v có nghĩa là vận tốc, getMagnitude (x) trả về gia tốc trên vị trí x.

Tôi đã thử thực hiện RK4:

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

Trong đó cơ thể hàm rk4 () là:

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;
}

Nhưng có gì đó không đúng, vì tôi chỉ tích hợp một lần bằng RK4 (tăng tốc). Việc tích hợp vận tốc bằng RK4 không có ý nghĩa gì vì nó giống như v * dt.

Bạn có thể cho tôi biết làm thế nào để giải các phương trình vi phân bậc hai bằng cách sử dụng tích hợp Runge-Kutta? Tôi có nên thực hiện RK4 bằng cách tính các hệ số k1, l1, k2, l2 ... l4 không? Làm thế nào tôi có thể làm điều đó?


Xin chào @Marcin, tôi đã chỉnh sửa tiêu đề của bạn để phản ánh tốt hơn những gì tôi nghĩ vấn đề của bạn thực sự là. Tôi nghĩ rằng chúng tôi có thể nhận được nhiều câu trả lời hữu ích hơn và nó sẽ dễ tìm kiếm hơn cho những người khác thấy câu hỏi này trong tương lai với tiêu đề mới. Hãy thay đổi nó trở lại nếu bạn không đồng ý.
Doug Lipinski

Câu trả lời:


17

Dường như có khá nhiều nhầm lẫn về cách áp dụng các phương pháp nhiều bước (ví dụ Runge-Kutta) cho các ODE bậc 2 hoặc cao hơn hoặc các hệ thống ODE. Quá trình này rất đơn giản một khi bạn hiểu nó, nhưng có lẽ không rõ ràng nếu không có lời giải thích tốt. Phương pháp sau đây là phương pháp tôi thấy đơn giản nhất.

F=mx¨

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

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

X=(x,v)RHS( t, X )(x˙(t),v˙(t))

Thật không may, C ++ không hỗ trợ các hoạt động vectơ như thế này nên bạn cần sử dụng thư viện vectơ, sử dụng các vòng lặp hoặc viết thủ công các phần riêng biệt. Trong C ++, bạn có thể sử dụng std::valarrayđể đạt được hiệu quả tương tự. Đây là một ví dụ làm việc đơn giản với gia tốc không đổi.

#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
" Thật không may, C ++ không hỗ trợ các hoạt động véc tơ như thế này " Tôi nghĩ rằng nó thậm chí còn trong thư viện chuẩn, nhưng không nhất thiết phải dễ sử dụng với các thư viện đại số tuyến tính khác: en.cppreference.com/w/cpp/numeric/valarray Tôi nghĩ các thư viện đại số tuyến tính phổ biến như Eigen, cũng nên được tính là "hỗ trợ".
Kirill

1
@Kirill, Cảm ơn vì tiền boa. Tôi vẫn còn khá mới đối với C ++ và tôi chưa từng sử dụng valarray trước đây, tôi cũng vừa học được một điều hữu ích! Chỉnh sửa để thêm.
Doug Lipinski

1
Có thể lời khuyên này cũng sẽ hữu ích sau đó: 1) Sử dụng định dạng clang để tự động định dạng mã của bạn, nó thực sự chuẩn và thống nhất. 2) Sử dụng typedef std::valarray<double> Vectorcho các loại thường được sử dụng. 3) Sử dụng const int NDIM = 2thay #definecho loại an toàn và chính xác. 4) Vì C ++ 11, bạn có thể thay thế cơ thể của RHS đơn giản bằng return {X[1], 1}. 5) Thực sự không phổ biến trong C ++ (không giống như C) trước tiên là khai báo biến, sau đó khởi tạo chúng, thích khai báo biến tại cùng nơi bạn khởi tạo chúng ( double t = 0., v.v.)
Kirill

1
@MarcinW. RHS()tính toán bên tay phải của phương trình vi phân. Vectơ trạng thái X là (x, v) nên dX / dt = (dx / dt, dv / dt) = (v, a). Đối với vấn đề của bạn (nếu a = G * M / x ^ 2) RHS sẽ trở lại { X[1], G*M/(X[0]*X[0]) }.
Doug Lipinski

1
@Kirill Tôi biết, nhưng nó chỉ hoạt động kể từ C ++ 11, có nghĩa là nó không hoạt động với các tùy chọn trình biên dịch mặc định trên các trình biên dịch phổ biến nhất. Tôi đã chọn bỏ qua điều đó để ủng hộ một cái gì đó phù hợp với các tiêu chuẩn cũ và hy vọng giảm sự nhầm lẫn gây ra bởi việc không thể biên dịch mã.
Doug Lipinski
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.