C#中的N体模拟

N body simulation in C#

我正在尝试使用 Runge Kutta 4 或 Velocity Verlet 集成算法在 C# 中实现 N 体模拟。

在我移动到更多粒子之前,我想通过模拟地球绕太阳的轨道来测试模拟,但是,由于某种原因,我得到了一个奇怪的螺旋而不是椭圆轨道。

我无法弄清楚这个问题,因为我使用相同的算法对太阳系进行了更简单的模拟,其中太阳固定在适当的位置并且一切正常。集成商工作得很好,因为无论我使用哪一个,我都能得到螺旋式增长。

如有任何帮助,我们将不胜感激。

代码如下:

class NBODY
{
    public static double G = 4 * Math.PI * Math.PI;

    class Particle
    {
        public double[] r;       // position vector
        public double[] v;       // velocity vector
        public double mass;    

        //constructor
        public Particle() {}
        public Particle(double x, double y, double z, double vx, double vy, double vz, double m)
        {
            this.r = new double[3];
            this.v = new double[3];

            this.r[0] = x;
            this.r[1] = y;
            this.r[2] = z;
            this.v[0] = vx;
            this.v[1] = vy;
            this.v[2] = vz;
            this.mass = m;
        }

        public void Update(Particle[] particles, double t, double h, int particleNumber)
        {
            RungeKutta4(particles, t, h, particleNumber);
        }

        private double acc(double r, Particle[] particles, int particleNumber, double[] r_temp, int l)
        {

            // dv/dt = f(x) = -G * m_i * (x - x_i) / [(x - x_i)^2 + (y - y_i)^2 + (z - z_i)^2]^(3/2)

            double sum = 0;

            switch (l)
            {
                case 0:
                    for (int i = 0; i < particles.Length; i++)
                        if (i != particleNumber)
                            sum += particles[i].mass * (r - particles[i].r[l]) / Math.Pow( Math.Pow(r - particles[i].r[l], 2)
                                + Math.Pow(r_temp[1] - particles[i].r[1], 2) + Math.Pow(r_temp[2] - particles[i].r[2], 2), 1.5);
                    break;

                case 1:
                    for (int i = 0; i < particles.Length; i++)
                        if (i != particleNumber)
                            sum += particles[i].mass * (r - particles[i].r[l]) / Math.Pow(Math.Pow(r - particles[i].r[l], 2)
                                + Math.Pow(r_temp[0] - particles[i].r[0], 2) + Math.Pow(r_temp[2] - particles[i].r[2], 2), 1.5);
                    break;

                case 2:
                    for (int i = 0; i < particles.Length; i++)
                        if (i != particleNumber)
                            sum += particles[i].mass * (r - particles[i].r[l]) / Math.Pow(Math.Pow(r - particles[i].r[l], 2)
                                + Math.Pow(r_temp[0] - particles[i].r[0], 2) + Math.Pow(r_temp[1] - particles[i].r[1], 2), 1.5);
                    break;
            }

            return -G * sum;
        }

        private void RungeKutta4(Particle[] particles, double t, double h, int particleNumber)
        {
            //current position of the particle is saved in a vector
            double[] r_temp = new double[3];

            for (int j = 0; j < 3; j++)
                r_temp[j] = this.r[j];

            //loop going over all the coordinates and updating each using RK4 algorithm
            for (int l = 0; l < 3; l++)
            {   
                double[,] k = new double[4, 2];

                k[0, 0] = this.v[l];                                                                //k1_r
                k[0, 1] = acc(this.r[l], particles, particleNumber, r_temp, l);                     //k1_v

                k[1, 0] = this.v[l] + k[0, 1] * 0.5 * h;                                            //k2_r
                k[1, 1] = acc(this.r[l] + k[0, 0] * 0.5 * h, particles, particleNumber, r_temp, l); //k2_v

                k[2, 0] = this.v[l] + k[1, 1] * 0.5 * h;                                            //k3_r
                k[2, 1] = acc(this.r[l] + k[1, 0] * 0.5 * h, particles, particleNumber, r_temp, l); //k3_v

                k[3, 0] = this.v[l] + k[2, 1] * h;                                                  //k4_r
                k[3, 1] = acc(this.r[l] + k[2, 0] * h, particles, particleNumber, r_temp, l);       //k4_v

                this.r[l] += (h / 6.0) * (k[0, 0] + 2 * k[1, 0] + 2 * k[2, 0] + k[3, 0]);
                this.v[l] += (h / 6.0) * (k[0, 1] + 2 * k[1, 1] + 2 * k[2, 1] + k[3, 1]);   
            }
        }

        /*
            Velocity Verlet algorithm:
            1. Calculate y(t+h) = y(t) + v(t)h + 0.5a(t)h*h
            2. Derive a(t+h) from dv/dt = -y using y(t+h)
            3. Calculate v(t+h) = v(t) + 0.5*(a(t) + a(t+h))*h
        */
        private void VelocityVerlet(Particle[] particles, double t, double h, int particleNumber)
        {

            double[] r_temp = new double[3];

            for (int j = 0; j < 3; j++)
                r_temp[j] = this.r[j];

            //loop going over all the coordinates and updating each using RK4 algorithm
            for (int l = 0; l < 3; l++)
            {
                //position
                this.r[l] += h * this.v[l] + 0.5 * h * h * acc(this.r[l], particles, particleNumber, r_temp, l);
                //velocity
                this.v[l] += 0.5 * h * (acc(r_temp[l], particles, particleNumber, r_temp,l)
                    + acc(this.r[l], particles, particleNumber, r_temp,l));
            }
        }     


    }

    static void Main(string[] args)
    {
        //output file
        TextWriter output = new StreamWriter("ispis.txt");

        // declarations of variables
        Particle[] particles = new Particle[2];
        particles[0] = new Particle(0, 0, 0, 0, 0, 0, 1);                  //sun
        particles[1] = new Particle(1, 0, 0, 0, 6.28, 0,  3.003467E-06);   //earth


        int N = 200;
        double h, t, tmax;
        double[,,] x = new double[particles.Length, N, 3];  //output


        //  setting initial values, step size and max time tmax
        h = 0.01;   // the step size in years
        tmax = h * N;

        // initial time        
        t = 0;

        int i = 0;

        while (t <= tmax) {

            //updates position of all particles
            for (int z = 1; z < particles.Length; z++)
                particles[z].Update(particles, t, h, z);

            //saves the position for output
            for (int j = 1; j < particles.Length ; j++)
                for (int z = 0; z < 3; z++ )
                    x[j,i,z] = particles[j].r[z];

            t += h;
            i++;
        }

        //output to file
        for (int k = 0; k < particles.Length; k++ )
        {
            for (int f = 0; f < 3; f++)

            {
                for (int l = 0; l < N; l++)
                    output.Write(string.Format("{0,-15:0.########},", x[k,l,f]));
                output.Write(string.Format("\n\n"));
            }
            output.Write(string.Format("\n\n\n\n"));
        }

        output.Close();
    }
}

这是地球轨道的输出数据图:

您的模型两次计算两个粒子之间的重力:对于第一个粒子,力基于它们的原始坐标,对于第二个粒子,它基于第一个粒子的更新位置。这明显违反了牛顿第三定律。您必须在任何更新之前预先计算所有力。

你的地球轨道问题是因为地球-太阳系统的重心,如果你想看到轨道保持在循环中,你需要设置重心在 (x,y,z) =(0,0,0);

我有一个基于您上面代码的 C# 代码,所以:

public partial class Form1 : Form
{
    static 
        int
        x1,y1,x2,y2, x3, y3;//for the 3th particule
    private void timer1_Tick_1(object sender, EventArgs e)
    {Moveu();
        Invalidate();
    }

    private void button1_Click_1(object sender, EventArgs e)
    {
        timer1.Enabled = !timer1.Enabled;
    }

    public Form1()
    {

        InitializeComponent();
        Paint += new PaintEventHandler(paint);
        MouseDown += new MouseEventHandler(mouse_Click);
        MouseUp += new MouseEventHandler(mouse_up);
        MouseMove += new MouseEventHandler(mouse_move);

        //                          x  y  z     vx   vy  vz    m   
        particles[0] = new Particle( 0, 0, 0,    0,  0, 0,    1   ) ;  //sun
        particles[1] = new Particle( 1, 0, 0,    0,  6, 0,    0.03 );   //earth
     // particles[2] = new Particle( 0, 2, 0,    0,  0, 0,    1   );   //planet

                x1 = (int)(100 * particles[0].r[0] + 300);
                y1 = (int)(100 * particles[0].r[1] + 300);

                x2 = (int)(100 * particles[1].r[0] + 300);
                y2 = (int)(100 * particles[1].r[1] + 300);



    }

    Particle[] particles = new Particle[2];

    void Moveu()
    {
        double h, t;

        //  setting initial values, step size and max time tmax
        h = 0.005;   // the step size in years


        // initial time        
        t = 0;
                //updates position of --all-- particles   ( z=0 not z=1 )
                for (int z = 0; z < particles.Length; z++)
                particles[z].RungeKutta4(particles, t, h, z);

                x1 = (int)(100 * particles[0].r[0] + 300);  //  +300 just for render it in centre
                y1 = (int)(100 * particles[0].r[1] + 300);

                x2 = (int)(100 * particles[1].r[0] + 300);
                y2 = (int)(100 * particles[1].r[1] + 300);

             // x3 = (int)(100 * particles[2].r[0] + 300);
             // y3 = (int)(100 * particles[2].r[1] + 300);

    }



    void paint(object s, PaintEventArgs e)
    {
        Graphics graf;
        graf = CreateGraphics();

        graf.FillEllipse(new SolidBrush(Color.AntiqueWhite), x1 + move.X, y1 + move.Y, 50, 50);
        graf.FillEllipse(new SolidBrush(Color.Blue),         x2 + move.X, y2 + move.Y, 10, 10);
   //   graf.FillEllipse(new SolidBrush(Color.Yellow), x3, y3, 20, 20);


    }

    class Particle
    {
        public double[] r;       // position vector
        public double[] v;       // velocity vector
        public double mass;

        //constructor
        public Particle() { }
        public Particle(double x, double y, double z, double vx, double vy, double vz, double m)
        {
            this.r = new double[3];
            this.v = new double[3];

            this.r[0] = x;
            this.r[1] = y;
            this.r[2] = z;
            this.v[0] = vx;
            this.v[1] = vy;
            this.v[2] = vz;
            this.mass = m;
        }



        private double acc(double r, Particle[] particles, int particleNumber, double[] r_temp, int l)
        {

            // dv/dt = f(x) = -G * m_i * (x - x_i) / [(x - x_i)^2 + (y - y_i)^2 + (z - z_i)^2]^(3/2)

            double sum = 0;

            switch (l)
            {
                case 0:
                    for (int i = 0; i < particles.Length; i++)
                        if (i != particleNumber)
                            sum += particles[i].mass * (r - particles[i].r[l]) / Math.Pow(Math.Pow(r - particles[i].r[l], 2)
                                + Math.Pow(r_temp[1] - particles[i].r[1], 2) + Math.Pow(r_temp[2] - particles[i].r[2], 2), 1.5);
                    break;

                case 1:
                    for (int i = 0; i < particles.Length; i++)
                        if (i != particleNumber)
                            sum += particles[i].mass * (r - particles[i].r[l]) / Math.Pow(Math.Pow(r - particles[i].r[l], 2)
                                + Math.Pow(r_temp[0] - particles[i].r[0], 2) + Math.Pow(r_temp[2] - particles[i].r[2], 2), 1.5);
                    break;

                case 2:
                    for (int i = 0; i < particles.Length; i++)
                        if (i != particleNumber)
                            sum += particles[i].mass * (r - particles[i].r[l]) / Math.Pow(Math.Pow(r - particles[i].r[l], 2)
                                + Math.Pow(r_temp[0] - particles[i].r[0], 2) + Math.Pow(r_temp[1] - particles[i].r[1], 2), 1.5);
                    break;
            }

            return -G * sum;
        }

        public void RungeKutta4(Particle[] particles, double t, double h, int particleNumber)
        {
            //current position of the particle is saved in a vector
            double[] r_temp = new double[3];

            for (int j = 0; j < 3; j++)
                r_temp[j] = this.r[j];

            //loop going over all the coordinates and updating each using RK4 algorithm
            for (int l = 0; l < 3; l++)
            {
                double[,] k = new double[4, 2];

                k[0, 0] = this.v[l];                                                                //k1_r
                k[0, 1] = acc(this.r[l], particles, particleNumber, r_temp, l);                     //k1_v

                k[1, 0] = this.v[l] + k[0, 1] * 0.5 * h;                                            //k2_r
                k[1, 1] = acc(this.r[l] + k[0, 0] * 0.5 * h, particles, particleNumber, r_temp, l); //k2_v

                k[2, 0] = this.v[l] + k[1, 1] * 0.5 * h;                                            //k3_r
                k[2, 1] = acc(this.r[l] + k[1, 0] * 0.5 * h, particles, particleNumber, r_temp, l); //k3_v

                k[3, 0] = this.v[l] + k[2, 1] * h;                                                  //k4_r
                k[3, 1] = acc(this.r[l] + k[2, 0] * h, particles, particleNumber, r_temp, l);       //k4_v

                this.r[l] += (h / 6.0) * (k[0, 0] + 2 * k[1, 0] + 2 * k[2, 0] + k[3, 0]);
                this.v[l] += (h / 6.0) * (k[0, 1] + 2 * k[1, 1] + 2 * k[2, 1] + k[3, 1]);
            }
        }




    }

    public static double G = 4 * Math.PI * Math.PI;   //then time unite in years and length unite = distance between Earth and Sun and masse is the sun masse unite

    void mouse_Click(object o, MouseEventArgs e)
    {
        dwn = new Point(e.X, e.Y);

        if ("" + e.Button == "Left")
        {
            pos = move;
            clicked = true;
        }
    }

    void mouse_move(object o, MouseEventArgs e)
    {


        if (clicked)
        {
            move = new Point(e.X + pos.X - dwn.X, e.Y + pos.Y - dwn.Y);


            Invalidate();
        }

    }

    void mouse_up(object o, MouseEventArgs e)
    {
        clicked = false;

    }

    Point dwn, pos,move;
    bool clicked;

}`you need to create a Timer and a button [![enter image description here][1]][1]