Проблема
Я реализовал алгоритм верлета скорости для вычисления траекторий двух тел, взаимодействующих друг с другом гравитационно (только ньютоновская гравитация). Меньшее тело, вращающееся по орбите, имеет очень маленькую массу, тело, находящееся в центре орбиты, имеет большую массу.
Теоретически скорость Верле не должна изменять полную энергию системы (она будет колебаться, но со временем среднее значение останется близким к начальной энергии).
Однако на практике я наблюдал увеличение энергии с течением времени.
Результаты
Вот несколько результатов, которые иллюстрируют проблему. Все симуляции были выполнены с временным шагом dt = 0,001. Облетавшееся на орбиту тело имело массу 1000, а гравитационная постоянная Вселенной была установлена на G = 1.0.
Во всех случаях начальное положение меньшего тела было {0, 0, 1}, а его начальная скорость была {0, 32, 0}. Начальная скорость большего тела была {0,0,0}.
Случай 1 (небольшая масса тела = 0,00001)
Вот траектория меньшего тела:
А вот энергия более 100к шагов.
Как мы видим, энергия сильно не меняется. Небольшие изменения вероятны из-за неточностей в расчетах.
Случай 1 (небольшая масса тела = 0,001)
Вот траектория орбитального тела:
А вот полная энергия:
Как мы видим, сейчас система набирает энергию.
Случай 3 (небольшая масса тела = 1)
Вот траектория орбитального тела:
А вот полная энергия:
Сейчас мы набираем много энергии.
код
Вот исходный код, который выполняет все вычисления:
Код для продвигающегося интегратора:
void Universe::simulation_step()
{
for(std::size_t i=0; i<get_size(); i++)
{
// Verlet step 1: Compute v(t + dt/2) = v(t) + 0.5*dt*a(t)
const Vector3D<Real> vel_half_step = {
velocity(i, 0) + static_cast<Real>(0.5)*sim_config.timestep*acceleration(i, 0),
velocity(i, 1) + static_cast<Real>(0.5)*sim_config.timestep*acceleration(i, 1),
velocity(i, 2) + static_cast<Real>(0.5)*sim_config.timestep*acceleration(i, 2)
};
// Verlet step 2: Compute x(t + dt) = x(t) + v(t + dt/2)*dt
position(i, 0) += vel_half_step.x*sim_config.timestep;
position(i, 1) += vel_half_step.y*sim_config.timestep;
position(i, 2) += vel_half_step.z*sim_config.timestep;
// Verlet step 3: update forces and update acceleration.
const Vector3D<Real> forces = compute_net_grawitational_force(i);
acceleration(i, 0) = forces.x/mass(i);
acceleration(i, 1) = forces.y/mass(i);
acceleration(i, 2) = forces.z/mass(i);
// Verlet step 4: update velocity to the full timestep.
velocity(i, 0) = vel_half_step.x + static_cast<Real>(0.5)*sim_config.timestep*acceleration(i, 0);
velocity(i, 1) = vel_half_step.y + static_cast<Real>(0.5)*sim_config.timestep*acceleration(i, 1);
velocity(i, 2) = vel_half_step.z + static_cast<Real>(0.5)*sim_config.timestep*acceleration(i, 2);
}
sim_time += sim_config.timestep;
}
Вот код для вычисления чистой гравитационной силы, действующей на тело:
Vector3D<Real> Universe::compute_net_grawitational_force(std::size_t i)
{
Vector3D<Real> accumulated_force = {0,0,0};
const Vector3D<Real> r2 = {
position(i, 0),
position(i, 1),
position(i, 2)
};
const Real m1 = mass(i);
for(std::size_t k=0; k<get_size(); k++)
{
if(k == i)
continue;
const Vector3D<Real> distace_vec = {
r2.x - position(k, 0),
r2.y - position(k, 1),
r2.z - position(k, 2),
};
const Real distance = distace_vec.norm2();
// Compute term that will be multipled by distance vector.
const Real a = (-1*sim_config.G*m1*mass(k))/
(distance*distance*distance);
// Compute and add new force acting on the body.
accumulated_force.x += distace_vec.x*a;
accumulated_force.y += distace_vec.y*a;
accumulated_force.z += distace_vec.z*a;
}
return accumulated_force;
}
Вот код, реализующий norm2 ():
template<typename T>
struct Vector3D
{
T x;
T y;
T z;
T norm2() const
{
return sqrt(x*x + y*y + z*z);
}
};
Наконец, вот код, который вычисляет результаты, нанесенные ранее:
std::vector<Real> x, y, z, energy;
x.resize(NSTEPS);
y.resize(NSTEPS);
z.resize(NSTEPS);
energy.resize(NSTEPS);
for(std::size_t i=0; i<NSTEPS; i++)
{
universe.simulation_step();
const Vector3D<Real> pos1 =
{
universe.get_positions()(0, 0),
universe.get_positions()(0, 1),
universe.get_positions()(0, 2)
};
const Vector3D<Real> pos2 =
{
universe.get_positions()(1, 0),
universe.get_positions()(1, 1),
universe.get_positions()(1, 2)
};
x[i] = pos2.x;
y[i] = pos2.y;
z[i] = pos2.z;
// Compute total kinetic energy of the system.
const Vector3D<Real> vel1 =
{
universe.get_velocities()(0, 0),
universe.get_velocities()(0, 1),
universe.get_velocities()(0, 2),
};
const Vector3D<Real> vel2 =
{
universe.get_velocities()(1, 0),
universe.get_velocities()(1, 1),
universe.get_velocities()(1, 2),
};
const Real mass1 = universe.get_masses()(0);
const Real mass2 = universe.get_masses()(1);
const Real spd1 = vel1.norm2();
const Real spd2 = vel2.norm2();
energy[i] = (spd1*spd1)*mass1*static_cast<float>(0.5);
energy[i] += (spd2*spd2)*mass2*static_cast<float>(0.5);
// Compute total potential energy
const Vector3D<Real> distance_vec =
{
pos1.x - pos2.x,
pos1.y - pos2.y,
pos1.z - pos2.z
};
const Real G = universe.get_sim_config().G;
energy[i] += -G*((mass1*mass2)/distance_vec.norm2());
}
Тип Real
- float
.
Мои теории
Я новичок, когда дело касается численного интегрирования (поэтому я разместил этот вопрос здесь). Однако вот несколько теорий о том, что может быть неправильным:
- Когда дело доходит до n ›= 2, в алгоритме Velocity Verlet есть ловушка, и я в нее попал.
- Где-то в приведенном выше коде есть ошибка реализации, и я ее не вижу.
- Ошибки, связанные с вычислениями чисел с плавающей запятой, накапливаются из-за небольших перемещений большого тела. (Скорее всего, не тот случай, см. Правку ниже.)
- Во время попыток отладить это я столкнулся с
Energy drift
в моделировании молекулярной динамики. Может, вот что здесь происходит?
Не похоже, что орбита разваливается, но это не тот результат, которого я ожидал, и я хочу знать, почему.
Может ли кто-нибудь помочь мне разгадать эту загадку?
Редактировать:
Я проверил двойную точность, и единственное отличие состоит в том, что теперь энергия наименьшей вращающейся массы намного стабильнее.
Сейчас наблюдается тенденция к увеличению даже для самой маленькой массы. Это намекает на то, что это не проблема с точностью вычислений.