การใช้วิธี Runge-Kutta กับลำดับที่สองของ ODE


11

ฉันจะเปลี่ยนวิธีการของออยเลอร์โดยลำดับที่ 4 ของ Runge-Kutta เพื่อตัดสินว่าการเคลื่อนที่ของฤดูใบไม้ร่วงฟรีนั้นมีขนาดไม่คงที่ (เช่นการลดลงอิสระจาก 10,000 กม. เหนือพื้นดิน) อย่างไร

จนถึงตอนนี้ฉันเขียนการรวมอย่างง่ายโดยวิธีออยเลอร์:

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

ตัวแปร x หมายถึงตำแหน่งปัจจุบัน, v หมายถึงความเร็ว, getMagnitude (x) จะส่งกลับค่าความเร่งบนตำแหน่ง x

ฉันพยายามใช้ RK4:

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

โดยที่ 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;
}

แต่มีบางอย่างผิดปกติเพราะฉันรวมเพียงครั้งเดียวโดยใช้ RK4 (ความเร่ง) การบูรณาการความเร็วโดยใช้ RK4 ไม่เหมาะสมเพราะมันเหมือนกับ v * dt

คุณช่วยบอกฉันได้ไหมว่าจะแก้สมการอนุพันธ์อันดับสองโดยใช้การรวมเข้าด้วยกันของ Runge-Kutta ได้อย่างไร ฉันควรใช้ RK4 โดยการคำนวณ k1, l1, k2, l2 ... สัมประสิทธิ์ l4 หรือไม่? ฉันจะทำสิ่งนั้นได้อย่างไร


สวัสดี @Marcin ฉันแก้ไขชื่อของคุณเพื่อสะท้อนสิ่งที่ฉันคิดว่าปัญหาของคุณคือ ฉันคิดว่าเราอาจได้รับคำตอบที่เป็นประโยชน์มากขึ้นและจะสามารถค้นหาได้สำหรับผู้อื่นที่เห็นคำถามนี้ในอนาคตด้วยชื่อใหม่ อย่าลังเลที่จะเปลี่ยนกลับหากคุณไม่เห็นด้วย
Doug Lipinski

คำตอบ:


17

ดูเหมือนจะมีความสับสนเล็กน้อยเกี่ยวกับวิธีการใช้วิธีการหลายขั้นตอน (เช่น Runge-Kutta) กับลำดับที่ 2 หรือสูงกว่าเพื่อ ODEs หรือระบบของ ODE กระบวนการนี้ง่ายมากเมื่อคุณเข้าใจ แต่อาจไม่ชัดเจนหากไม่มีคำอธิบายที่ดี วิธีการต่อไปนี้เป็นวิธีที่ฉันพบง่ายที่สุด

ในกรณีของคุณสมการเชิงอนุพันธ์ที่คุณต้องการที่จะแก้ปัญหาคือ{x} ขั้นตอนแรกคือการเขียน ODE ลำดับที่สองนี้เป็นระบบของ ODE ลำดับที่หนึ่ง สิ่งนี้จะทำตามF=ม.x¨

[x˙โวลต์˙]=[โวลต์F/ม.]

สมการทั้งหมดในระบบนี้จะต้องแก้ไขพร้อมกันซึ่งก็คือบอกว่าคุณไม่ควรเลื่อนแล้วเลื่อนพวกเขาทั้งคู่ควรจะก้าวหน้าในเวลาเดียวกัน ในภาษาที่รองรับการทำงานของเวกเตอร์โดยไม่มีการวนซ้ำทำได้ง่าย ๆ โดยทำข้อกำหนดที่จำเป็นทั้งหมดในเวกเตอร์โค้ดที่มีความยาว 2 ฟังก์ชันที่คำนวณด้านขวามือ (อัตราการเปลี่ยนแปลง) ของ ODE ของคุณควรคืนค่าเวกเตอร์ที่มีความยาว 2 , การควรจะเป็นพาหะของความยาว 2 และตัวแปรของรัฐควรจะเป็นเวกเตอร์ของความยาว 2. ใน MATLAB รหัสที่จำเป็นสำหรับเวลาก้าวสามารถเขียนเป็น:โวลต์xk1k4(x,โวลต์)

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

ที่และผลตอบแทนที่มีเวกเตอร์(t)) อย่างที่คุณเห็นโดยการทำให้เวกเตอร์เป็นสิ่งที่คุณไม่จำเป็นต้องเปลี่ยนไวยากรณ์ของรหัส RK4 ไม่ว่าจะมีสมการกี่อันในระบบ ODE ของคุณX=(x,โวลต์)RHS( t, X )(x˙(เสื้อ),โวลต์˙(เสื้อ))

น่าเสียดายที่ C ++ ไม่สนับสนุนการทำงานของเวกเตอร์ในลักษณะนี้ดังนั้นคุณต้องใช้ไลบรารีเวกเตอร์ใช้ลูปหรือเขียนส่วนต่าง ๆ ด้วยตนเอง ใน C ++ คุณสามารถใช้std::valarrayเพื่อให้ได้ผลเหมือนกัน นี่คือตัวอย่างการทำงานที่เรียบง่ายพร้อมการเร่งความเร็วคงที่

#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
" น่าเสียดายที่ C ++ ไม่สนับสนุนการทำงานของเวกเตอร์แบบนี้ " ฉันคิดว่ามันทำในไลบรารีมาตรฐานแม้จะไม่จำเป็นต้องใช้งานง่ายกับไลบรารี่พีชคณิตเชิงเส้นอื่น ๆ : en.cppreference.com/w/cpp/numeric/valarrayฉันคิดว่า ไลบรารีพีชคณิตเชิงเส้นทั่วไปเช่น Eigen ก็ควรนับเป็น "สนับสนุน"
คิริลล์

1
@ Kirill ขอบคุณสำหรับเคล็ดลับ ฉันยังค่อนข้างใหม่กับ C ++ และฉันไม่เคยใช้วาลเลย์มาก่อนฉันเพิ่งเรียนรู้สิ่งที่มีประโยชน์เช่นกัน! กำลังแก้ไขเพื่อเพิ่ม
Doug Lipinski

1
บางทีคำแนะนำนี้อาจเป็นประโยชน์ในเวลาต่อมา: 1) ใช้รูปแบบเสียงดังกราวสำหรับการจัดรูปแบบโค้ดของคุณโดยอัตโนมัติซึ่งเป็นมาตรฐานและเครื่องแบบจริงๆ 2) ใช้typedef std::valarray<double> Vectorสำหรับประเภทที่ใช้กันทั่วไป 3) ใช้const int NDIM = 2แทน#defineเพื่อความปลอดภัยและความถูกต้องของประเภท 4) ตั้งแต่ C ++ 11 คุณสามารถแทนที่ร่างกายของ RHS return {X[1], 1}ก็มี 5) มันผิดปกติจริงๆใน C ++ (ไม่เหมือนกับ C) ในการประกาศตัวแปรก่อนแล้วจึงเริ่มต้นใหม่ในภายหลัง, ชอบการประกาศตัวแปรในที่เดียวกับที่คุณกำหนดค่าเริ่มต้น ( double t = 0.ฯลฯ )
Kirill

1
@MarcinW RHS()คำนวณทางด้านขวามือของสมการอนุพันธ์ เวกเตอร์สถานะ X คือ (x, v) ดังนั้น dX / dt = (dx / dt, dv / dt) = (v, a) สำหรับปัญหาของคุณ (ถ้า A = G * M / x ^ 2) RHS { X[1], G*M/(X[0]*X[0]) }ควรกลับ
Doug Lipinski

1
@ Kirill ฉันรู้ แต่ใช้งานได้ตั้งแต่ C ++ 11 เท่านั้นซึ่งหมายความว่ามันไม่ทำงานกับตัวเลือกคอมไพเลอร์เริ่มต้นในคอมไพเลอร์ที่ได้รับความนิยมมากที่สุด ฉันเลือกที่จะทิ้งสิ่งนั้นไว้ในความโปรดปรานของบางสิ่งที่ใช้งานได้กับมาตรฐานเก่าเช่นกันและหวังว่าจะลดความสับสนที่เกิดจากการไม่สามารถคอมไพล์โค้ดได้
Doug Lipinski
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.