Python N 体模拟代码给出错误答案

Python N-body simulation code giving incorrect answer

我已经编写了一些 python 代码来使用 Euler 方法解决 N 体问题。代码 运行 没有问题,似乎给出了合理的答案(例如,如果有两个粒子,则开始相互移动)。然而,当我 运行 这个模拟经过大量迭代时,我看到粒子(假设我 运行 它有两个粒子)相互经过(我不考虑碰撞)并继续前进方向无限期。这违反了能量守恒,所以我的代码中肯定有缺陷,但我找不到它。谁能找到它并解释我的错误。

谢谢。

感谢@samgak 指出我更新了两次粒子。我现在已经解决了这个问题,但问题仍然存在。我还复制了我在 运行 此模拟中得到的输出,其中两个静止粒子位于 (0,0) 和 (1,0),时间步长为 1 秒,迭代次数为 100000 次:

质量为 1 位置为 [234.8268420043934, 0.0] 速度为 [0.011249111128594091, 0.0] 的粒子

质量为 1 位置为 [-233.82684200439311, 0.0] 速度为 [-0.011249111128594091, 0.0]

的粒子

还要感谢@PM2Ring 指出我可以进行的一些优化以及使用欧拉方法的风险。

代码:

import math
class Particle:
    """
    Class to represent a single particle
    """
    def __init__(self,mass,position,velocity):
        """
        Initialize the particle
        """
        self.G = 6.67408*10**-11 #fixed throughout the simulation
        self.time_interval = 10**0 #fixed throughout the simulation, gives the interval between updates
        self.mass = mass
        self.position = position #should be a list
        self.velocity = velocity #should be a list
        self.updated_position = position
        self.updated_velocity = velocity
    def __str__(self):
        """
        String representation of particle
        """
        return "Particle with mass: " + str(self.mass) + " and position: " + str(self.position) + " and velocity: " + str(self.velocity)
    def get_mass(self):
        """
        Returns the mass of the particle
        """
        return self.mass
    def get_position(self):
        """
        returns the position of the particle
        """
        return self.position
    def get_velocity(self):
        """
        returns the velocity of the particle
        """
        return self.velocity
    def get_updated_position(self):
        """
        calculates the future position of the particle
        """
        for i in range(len(self.position)):
            self.updated_position[i] = self.updated_position[i] + self.time_interval*self.velocity[i]
    def update_position(self):
        """
        updates the position of the particle
        """
        self.position = self.updated_position.copy()
    def get_distance(self,other_particle):
        """
        returns the distance between the particle and another given particle
        """
        tot = 0
        other = other_particle.get_position()
        for i in range(len(self.position)):
            tot += (self.position[i]-other[i])**2
        return math.sqrt(tot)
    def get_updated_velocity(self,other_particle):
        """
        updates the future velocity of the particle due to the acceleration
        by another particle
        """
        distance_vector = []
        other = other_particle.get_position()
        for i in range(len(self.position)):
            distance_vector.append(self.position[i]-other[i])
        distance_squared = 0
        for item in distance_vector:
            distance_squared += item**2
        distance = math.sqrt(distance_squared)
        force = -self.G*self.mass*other_particle.get_mass()/(distance_squared)
        for i in range(len(self.velocity)):
            self.updated_velocity[i] = self.updated_velocity[i]+self.time_interval*force*(distance_vector[i])/(self.mass*(distance))
    def update_velocity(self):
        """
        updates the velocity of the particle
        """
        self.velocity = self.updated_velocity.copy()
def update_particles(particle_list):
    """
    updates the position of all the particles
    """
    for i in range(len(particle_list)):
        for j in range(i+1,len(particle_list)):
            particle_list[i].get_updated_velocity(particle_list[j])
            particle_list[j].get_updated_velocity(particle_list[i])
    for i in range(len(particle_list)):
        particle_list[i].update_velocity()
        particle_list[i].get_updated_position()
    for i in range(len(particle_list)):
        particle_list[i].update_position()      
#the list of particles
partList = [Particle(1,[0,0],[0,0]),Particle(1,[1,0],[0,0])]
#how many iterations I perform
for i in range(100000):
    update_particles(partList)
#prints out the final position of all the particles
for item in partList:
    print(item)

-------------------------------------------- ---------------------------------------------- ---------------------------------------------- ---------------------------------------------- ---------------------------------------------- ------------------进一步编辑:

我决定实施 Leapfrog 方法,我已经开发了一些代码,再次 运行s 并且似乎运行良好(至少在命令行中)。但是,当我添加绘图功能并对其进行分析时,似乎出现了另一个问题。系统似乎再次走得太远,能量再次无限制地增加。我附上了我得到的输出图片来展示问题。如果我再次只有两个质量相等的粒子,它们将再次相互通过并继续相互远离而不会停止。因此,我的代码中一定存在我没有发现的错误。

如果有人能提供帮助,将不胜感激。

我的代码:

import math
import matplotlib.pyplot as plt

class Particle:
    """
    Represents a single particle
    """
    def __init__(self,mass,position,velocity):
        """
        Initialize the particle
        """
        self.G = 6.67408*10**-11
        self.time_step = 10**2
        self.mass = mass
        self.dimensions = len(position)
        self.position = position
        self.velocity = velocity
        self.acceleration = [0 for i in range(len(position))]
        self.next_position = position
        self.next_velocity = velocity
        self.next_acceleration = [0 for i in range(len(position))]
    def __str__(self):
        """
        A string representation of the particle
        """
        return "A Particle with mass: " + str(self.mass) + " and position: " + str(self.position) + " and velocity:" + str(self.velocity) 
    def get_mass(self):
        return self.mass
    def get_position(self):
        return self.position
    def get_velocity(self):
        return self.velocity
    def get_acceleration(self):
        return self.acceleration
    def get_next_position(self):
        return self.next_position
    def put_next_position(self):
        for i in range(self.dimensions):
            self.next_position[i] = self.position[i] + self.time_step*self.velocity[i]+0.5*self.time_step**2*self.acceleration[i]
    def put_next_velocity(self):
        for i in range(self.dimensions):
            self.next_velocity[i] = self.velocity[i] + 0.5*self.time_step*(self.acceleration[i]+self.next_acceleration[i])
    def update_position(self):
        self.position = self.next_position.copy()
    def update_velocity(self):
        self.velocity = self.next_velocity.copy()  
    def update_acceleration(self):
        self.acceleration = self.next_acceleration.copy()
    def reset_acceleration(self):
        self.acceleration = [0 for i in range(self.dimensions)]
    def reset_future_acceleration(self):
        self.next_acceleration = [0 for i in range(self.dimensions)]
    def calculate_acceleration(self,other_particle):
        """
        Increments the acceleration of the particle due to the force from 
        a single other particle
        """
        distances = []
        other = other_particle.get_position()
        distance_squared = 0
        for i in range(self.dimensions):
            distance_squared += (self.position[i]-other[i])**2
            distances.append(self.position[i]-other[i])
        distance = math.sqrt(distance_squared)
        force = -self.G*self.mass*other_particle.get_mass()/distance_squared
        acc = []
        for i in range(self.dimensions):
            acc.append(force*distances[i]/(distance*self.mass))
        for i in range(self.dimensions):
            self.acceleration[i] += acc[i]
    def calculate_future_acceleration(self,other_particle):
        """
        Increments the future acceleration of the particle due to the force from 
        a single other particle
        """
        distances = []
        other = other_particle.get_next_position()
        distance_squared = 0
        for i in range(self.dimensions):
            distance_squared += (self.next_position[i]-other[i])**2
            distances.append(self.next_position[i]-other[i])
        distance = math.sqrt(distance_squared)
        force = -self.G*self.mass*other_particle.get_mass()/distance_squared
        acc = []
        for i in range(self.dimensions):
            acc.append(force*distances[i]/(distance*self.mass))
        for i in range(self.dimensions):
            self.next_acceleration[i] += acc[i]

def update_all(particleList):
    for i in range(len(particleList)):
        particleList[i].reset_acceleration()
        for j in range(len(particleList)):
            if i != j:
                particleList[i].calculate_acceleration(particleList[j])
    for i in range(len(particleList)):
        particleList[i].put_next_position()
    for i in range(len(particleList)):
        particleList[i].reset_future_acceleration()
        for j in range(len(particleList)):
            if i != j:
                particleList[i].calculate_future_acceleration(particleList[j])
    for i in range(len(particleList)):
        particleList[i].put_next_velocity()
    for i in range(len(particleList)):
        particleList[i].update_position()
        particleList[i].update_velocity()
partList = [Particle(1,[0,0],[0,0]),Particle(1,[1,0],[0,0])]

Alist = [[],[]]
Blist = [[],[]]
for i in range(10000):
    Alist[0].append(partList[0].get_position()[0])
    Alist[1].append(partList[0].get_position()[1])
    Blist[0].append(partList[1].get_position()[0])
    Blist[1].append(partList[1].get_position()[1])
    update_all(partList)

plt.scatter(Alist[0],Alist[1],color="r")
plt.scatter(Blist[0],Blist[1],color="b")
plt.grid() 
plt.show()
for item in partList:
    print(item)

谁能告诉我我的代码哪里出错了。

代码中的主要问题是它使用了 Euler 方法,随着迭代次数的增加,该方法非常不准确(仅 O(h) 与其他方法相比,可以是 O(h^4) 甚至更好).要解决这个问题,需要对代码进行根本性的重组,所以我会说这段代码对于 N 体模拟来说并不是很准确(它只适用于 2 个粒子,随着我添加的越来越多,错误只会增加)。

感谢@samgak 和@PM2Ring 帮助我消除了错误并优化了我的代码,但总的来说这段代码无法使用...

编辑:我已经从头开始实施了评论中提到的越级方法,并且发现它工作得很好。它非常易于理解和实施,而且效果很好!

进一步编辑:我以为我有 leapfrog 方法工作。结果发现其中还有另一个错误,我是在添加 GUI 功能时才看到的。