My colleague and I are trying to study the three-body problem, with different integration schemes, starting from the two-body problem. We implemented the symplectic Euler scheme and the Runge–Kutta 4th order in C++, and the trajectories obtained are the following.
As we can see, they are almost elliptical with Euler integration, while they become bigger and bigger with rk4, increasing the energy of the system at each revolution:
Is it possible that the rk4 increases its energy or do we have something wrong in our code?
#include <iostream>
#include <cmath>
#include <array>
#include <fstream>
#include "integrators.h"
#include <string>
#include <map>
// Value-Defintions of the different String values
enum StringValue { evNotDefined,
evStringValue1,
evStringValue2
};
// Map to associate the strings with the enum values
static std::map<std::string, StringValue> s_mapStringValues;
void Initialize(){
s_mapStringValues["euler"] = evStringValue1;
s_mapStringValues["rk4"] = evStringValue2;
}
static constexpr int DIM = 4;
static constexpr double G = 10;
static constexpr int N_BODIES = 3;
static constexpr int N_STEPS = 300000;
double distance(std::array<double, 3> r1, std::array<double, 3> r2){
return sqrt(pow(r1[0]-r2[0],2)+pow(r1[1]-r2[1],2)+pow(r1[2]-r2[2],2));
}
class Planet{
public:
double m;
std::array <double, 3> x;
std::array <double, 3> v;
std::array <double, 3> a;
double energy;
Planet (double mass, double x_position, double y_position, double z_position, double x_velocity, double y_velocity, double z_velocity) {
m = mass;
x[0] = x_position;
x[1] = y_position;
x[2] = z_position;
v[0] = x_velocity;
v[1] = y_velocity;
v[2] = z_velocity;
};
void computeKineticEnergy(){
energy = 0.5 * m * (pow(v[0],2)+pow(v[1],2)+pow(v[2],2));
}
};
double computePotentialEnergy(Planet planet1, Planet planet2, Planet planet3){
return -1 * G * ( planet3.m * planet1.m / distance(planet3.x, planet1.x) + planet3.m * planet2.m / distance(planet3.x, planet2.x) + planet1.m * planet2.m / distance(planet1.x, planet2.x));
}
double acceleration(Planet A, Planet B, Planet C, int axe){
// Compute the acceleration of the body C, specifying the axis.
double mass_A = A.m;
double mass_B = B.m;
double posx_A = A.x[0];
double posx_B = B.x[0];
double posx_C = C.x[0];
double posy_A = A.x[1];
double posy_B = B.x[1];
double posy_C = C.x[1];
double posz_A = A.x[2];
double posz_B = B.x[2];
double posz_C = C.x[2];
if (axe == 0){
return (-1 * G * (mass_A * (posx_C-posx_A) / pow(sqrt(pow(posx_C-posx_A,2)+pow(posy_C-posy_A,2)+pow(posz_C-posz_A,2)), 3) + mass_B * (posx_C-posx_B) / pow(sqrt(pow(posx_C-posx_B,2)+pow(posy_C-posy_B,2)+pow(posz_C-posz_B,2)), 3)));
}else if (axe == 1){
return (-1 * G * (mass_A * (posy_C-posy_A) / pow(sqrt(pow(posx_C-posx_A,2)+pow(posy_C-posy_A,2)+pow(posz_C-posz_A,2)), 3) + mass_B * (posy_C-posy_B) / pow(sqrt(pow(posx_C-posx_B,2)+pow(posy_C-posy_B,2)+pow(posz_C-posz_B,2)), 3)));
}else if (axe == 2){
return (-1 * G * (mass_A * (posz_C-posz_A) / pow(sqrt(pow(posx_C-posx_A,2)+pow(posy_C-posy_A,2)+pow(posz_C-posz_A,2)), 3) + mass_B * (posz_C-posz_B) / pow(sqrt(pow(posx_C-posx_B,2)+pow(posy_C-posy_B,2)+pow(posz_C-posz_B,2)), 3)));
}
}
double F(double x, double v, double t, Planet A, Planet B, Planet C, int j ){
// Function to integrate via Runge-Kutta.
double mass_A = A.m;
double mass_B = B.m;
double posx_A = A.x[0];
double posx_B = B.x[0];
double posx_C = C.x[0];
double posy_A = A.x[1];
double posy_B = B.x[1];
double posy_C = C.x[1];
double posz_A = A.x[2];
double posz_B = B.x[2];
double posz_C = C.x[2];
if (j == 0){
return (-1 * G * (mass_A * (x-posx_A) / pow(sqrt(pow(x-posx_A,2)+pow(posy_C-posy_A,2)+pow(posz_C-posz_A,2)), 3) + mass_B * (x-posx_B) / pow(sqrt(pow(x-posx_B,2)+pow(posy_C-posy_B,2)+pow(posz_C-posz_B,2)), 3)));
}else if (j == 1) {
return (-1 * G * (mass_A * (x-posy_A) / pow(sqrt(pow(posx_C-posx_A,2)+pow(x-posy_A,2)+pow(posz_C-posz_A,2)), 3) + mass_B * (x-posy_B) / pow(sqrt(pow(posx_C-posx_B,2)+pow(x-posy_B,2)+pow(posz_C-posz_B,2)), 3)));
}else if (j == 2) {
return (-1 * G * (mass_A * (x-posz_A) / pow(sqrt(pow(posx_C-posx_A,2)+pow(posy_C-posy_A,2)+pow(x-posz_A,2)), 3) + mass_B * (x-posz_B) / pow(sqrt(pow(posx_C-posx_B,2)+pow(posy_C-posy_B,2)+pow(x-posz_B,2)), 3)));
}
}
int main(int argc, char** argv){
double h = 0.0005;
Planet A(100, -10, 10, -11, -3, 0, 0);
Planet B(100, 0, 0, 0, 3, 0, 0);
Planet C(0, 10, 14, 12, 3, 0, 0);
double x_A[DIM][3];
double x_B[DIM][3];
double x_C[DIM][3];
double vx_A;
double vy_A;
double vz_A;
double vx_B;
double vy_B;
double vz_B;
double vx_C;
double vy_C;
double vz_C;
double mass_A = A.m;
double mass_B = B.m;
double mass_C = C.m;
x_A[0][0] = A.x[0];
x_B[0][0] = B.x[0];
x_C[0][0] = C.x[0];
vx_A = A.v[0];
vx_B = B.v[0];
vx_C = C.v[0];
x_A[1][0] = A.x[1];
x_B[1][0] = B.x[1];
x_C[1][0] = C.x[1];
vy_A = A.v[1];
vy_B = B.v[1];
vy_C = C.v[1];
x_A[2][0] = A.x[2];
x_B[2][0] = B.x[2];
x_C[2][0] = C.x[2];
vz_A = A.v[2];
vz_B = B.v[2];
vz_C = C.v[2];
Initialize();
std::ofstream file_energy("Total_energy_" + std::string(argv[1]) + ".csv");
std::ofstream output_file_A("positions_A_" + std::string(argv[1]) + ".csv");
std::ofstream output_file_B("positions_B_" + std::string(argv[1]) + ".csv");
std::ofstream output_file_C("positions_C_" + std::string(argv[1]) + ".csv");
output_file_A<<"x;y;z"<<std::endl;
output_file_B<<"x;y;z"<<std::endl;
output_file_C<<"x;y;z"<<std::endl;
file_energy<<"k;p"<<std::endl;
double m1;
double k1;
double m2;
double k2;
double m3;
double k3;
double m4;
double k4;
std::array<double,3> vA = A.v; //condizione iniziale velocita
std::array<double,3> xA = A.x;
std::array<double,3> vB = B.v; //condizione iniziale velocita
std::array<double,3> xB = B.x;
std::array<double,3> vC = C.v; //condizione iniziale velocita
std::array<double,3> xC = C.x;
double t;
std::array<double,3> cm = computeCM(A, B, C);
if (argc>=2){
switch (s_mapStringValues[argv[1]]){
case evStringValue1:
// ==========================================================
// EULER
// ==========================================================
for (int i=0; i<N_STEPS-1; i++){
output_file_A << A.x[0] << ";" << A.x[1] << ";" << A.x[2]<< std::endl;
output_file_B << B.x[0] << ";" << B.x[1] << ";" << B.x[2]<< std::endl;
output_file_C << C.x[0] << ";" << C.x[1] << ";" << C.x[2]<< std::endl;
for(int j=0; j<DIM-1; j++){
A.a[j] = acceleration(B, C, A, j);
B.a[j] = acceleration(A, C, B, j);
C.a[j] = acceleration(B, A, C, j);
A.v[j] += A.a[j] * h;
B.v[j] += B.a[j] * h;
C.v[j] += C.a[j] * h;
x_A[j][0] = x_A[j][0] + A.v[j] * h;
x_B[j][0] = x_B[j][0] + B.v[j] * h;
x_C[j][0] = x_C[j][0] + C.v[j] * h;
}
for (int j=0;j<DIM-1;j++){
A.x[j] = x_A[j][0];
B.x[j] = x_B[j][0];
C.x[j] = x_C[j][0];
}
A.computeKineticEnergy();
B.computeKineticEnergy();
C.computeKineticEnergy();
cm = computeCM(A,B,C);
file_energy<<A.energy + B.energy + C.energy<<";"<< computePotentialEnergy(A, B, C)<<std::endl;
}
break;
case evStringValue2:{
// ==========================================================
// RUNGE KUTTA 4
// ==========================================================
for(int i=0; i<N_STEPS-1; i++){
for(int j=0; j<DIM-1; j++){
// body A
m1 = h*vA[j];
k1 = h*F(xA[j], vA[j], t, C, B, A, j);
m2 = h*(vA[j] + 0.5*k1);
k2 = h*F(xA[j]+0.5*m1, vA[j]+0.5*k1, t+0.5*h, C, B, A, j);
m3 = h*(vA[j] + 0.5*k2);
k3 = h*F(xA[j]+0.5*m2, vA[j]+0.5*k2, t+0.5*h, C, B, A, j);
m4 = h*(vA[j] + k3);
k4 = h*F(xA[j]+m3, vA[j]+k3, t+h, C, B, A, j);
xA[j] += (m1 + 2*m2 + 2*m3 + m4)/6;
vA[j] += (k1 + 2*k2 + 2*k3 + k4)/6;
}
for(int j=0; j<DIM-1; j++){
// body B
m1 = h*vB[j];
k1 = h*F(xB[j], vB[j], t, A, C, B, j); //(x, v, t)
m2 = h*(vB[j] + 0.5*k1);
k2 = h*F(xB[j]+0.5*m1, vB[j]+0.5*k1, t+0.5*h, A, C, B, j);
m3 = h*(vB[j] + 0.5*k2);
k3 = h*F(xB[j]+0.5*m2, vB[j]+0.5*k2, t+0.5*h, A, C, B, j);
m4 = h*(vB[j] + k3);
k4 = h*F(xB[j]+m3, vB[j]+k3, t+h, A, C, B, j);
xB[j] += (m1 + 2*m2 + 2*m3 + m4)/6;
vB[j] += (k1 + 2*k2 + 2*k3 + k4)/6;
}
for(int j=0; j<DIM-1; j++){
// body C
m1 = h*vC[j];
k1 = h*F(xC[j], vC[j], t, A, B, C, j); //(x, v, t)
m2 = h*(vC[j] + 0.5*k1);
k2 = h*F(xC[j]+0.5*m1, vC[j]+0.5*k1, t+0.5*h, A, B, C, j);
m3 = h*(vC[j] + 0.5*k2);
k3 = h*F(xC[j]+0.5*m2, vC[j]+0.5*k2, t+0.5*h, A, B, C, j);
m4 = h*(vC[j] + k3);
k4 = h*F(xC[j]+m3, vC[j]+k3, t+h, A, B, C, j);
xC[j] += (m1 + 2*m2 + 2*m3 + m4)/6;
vC[j] += (k1 + 2*k2 + 2*k3 + k4)/6;
}
for(int j=0; j<DIM-1;j++){
A.v[j] = vA[j];
B.v[j] = vB[j];
C.v[j] = vC[j];
A.x[j] = xA[j];
B.x[j] = xB[j];
C.x[j] = xC[j];
}
output_file_A << A.x[0] << ";" << A.x[1] << ";" << A.x[2]<< std::endl;
output_file_B << B.x[0] << ";" << B.x[1] << ";" << B.x[2]<< std::endl;
output_file_C << C.x[0] << ";" << C.x[1] << ";" << C.x[2]<< std::endl;
A.computeKineticEnergy();
B.computeKineticEnergy();
C.computeKineticEnergy();
file_energy<<A.energy + B.energy + C.energy <<";"<< computePotentialEnergy(A, B, C)<<std::endl;
}
}
break;
default:
std::cout<<"Insert an argument between: euler, or rk4"<<std::endl;
return 0;
}
}else{
std::cout<<"Insert an argument between: euler, or rk4"<<std::endl;
return 0;
}
//----------------------------------------------------------------
output_file_A.close();
output_file_B.close();
output_file_C.close();
file_energy.close();
return 0;
}
edit:
I think I have understood the problem. I have modified the code as follow, but now the energy decreases a lot and the trajectories become more and more little.
for(int i=0; i<N_STEPS-1; i++){
for(int j=0; j<DIM-1; j++){
// body A
m1[0][j] = h*vA[j];
k1[0][j] = h*F(xA[j], vA[j], t, C, B, A, j);
// body B
m1[1][j] = h*vA[j];
k1[1][j] = h*F(xA[j], vA[j], t, C, A, B, j);
// body C
m1[2][j] = h*vA[j];
k1[2][j] = h*F(xA[j], vA[j], t, A, B, C, j);
}
for(int j=0; j<DIM-1;j++){
A.v[j] += 0.5 * k1[0][j];
B.v[j] += 0.5 * k1[1][j];
C.v[j] += 0.5 * k1[2][j];
A.x[j] += 0.5 * m1[0][j];
B.x[j] += 0.5 * m1[1][j];
C.x[j] += 0.5 * m1[2][j];
}
for(int j=0; j<DIM-1; j++){
//Body A
m2[0][j] = h*(A.v[j]);
k2[0][j] = h*F(A.x[j], A.v[j], t+0.5*h, C, B, A, j);
//Body B
m2[1][j] = h*(B.v[j]);
k2[1][j] = h*F(B.x[j], B.v[j], t+0.5*h, C, A, B, j);
// Body C
m2[2][j] = h*(C.v[j]);
k2[2][j] = h*F(C.x[j], C.v[j], t+0.5*h, A, B, C, j);
}
for(int j=0; j<DIM-1;j++){
A.v[j] += 0.5 * k2[0][j];
B.v[j] += 0.5 * k2[1][j];
C.v[j] += 0.5 * k2[2][j];
A.x[j] += 0.5 * m2[0][j];
B.x[j] += 0.5 * m2[1][j];
C.x[j] += 0.5 * m2[2][j];
}
for(int j=0; j<DIM-1; j++){
//Body A
m3[0][j] = h*(A.v[j]);
k3[0][j] = h*F(A.x[j], A.v[j], t+0.5*h, C, B, A, j);
//Body B
m3[1][j] = h*(B.v[j]);
k3[1][j] = h*F(B.x[j], B.v[j], t+0.5*h, C, A, B, j);
// Body C
m3[2][j] = h*(C.v[j]);
k3[2][j] = h*F(C.x[j], C.v[j], t+0.5*h, A, B, C, j);
}
for(int j=0; j<DIM-1;j++){
A.v[j] += k3[0][j];
B.v[j] += k3[1][j];
C.v[j] += k3[2][j];
A.x[j] += m3[0][j];
B.x[j] += m3[1][j];
C.x[j] += m3[2][j];
}
for(int j=0; j<DIM-1; j++){
//Body A
m4[0][j] = h*(A.v[j]);
k4[0][j] = h*F(A.x[j], A.v[j], t + h, C, B, A, j);
//Body B
m4[1][j] = h*(B.v[j]);
k4[1][j] = h*F(B.x[j], B.v[j], t + h, C, A, B, j);
// Body C
m4[2][j] = h*(C.v[j]);
k4[2][j] = h*F(C.x[j], C.v[j], t + h, A, B, C, j);
}
for(int j=0; j<DIM-1; j++){
xA[j] += (m1[0][j] + 2*m2[0][j] + 2*m3[0][j] + m4[0][j])/6;
vA[j] += (k1[0][j] + 2*k2[0][j] + 2*k3[0][j] + k4[0][j])/6;
xB[j] += (m1[1][j] + 2*m2[1][j] + 2*m3[1][j] + m4[1][j])/6;
vB[j] += (k1[1][j] + 2*k2[1][j] + 2*k3[1][j] + k4[1][j])/6;
xC[j] += (m1[2][j] + 2*m2[2][j] + 2*m3[2][j] + m4[2][j])/6;
vC[j] += (k1[2][j] + 2*k2[2][j] + 2*k3[2][j] + k4[2][j])/6;
}
for(int j=0; j<DIM-1;j++){
A.v[j] = vA[j];
B.v[j] = vB[j];
C.v[j] = vC[j];
A.x[j] = xA[j];
B.x[j] = xB[j];
C.x[j] = xC[j];
}
Edit 2;
Energy of the system with the proposed solution: []