Solar System Simulation Project (velocity verlet help)

别说谁变了你拦得住时间么 提交于 2019-12-04 17:38:00

The facts that your integrator accepts scalars and that your question is about 2-dimensional system makes me think that you are calling the integrator twice, once for each component. This simply won't work since your system will be taking unrealistic moves through the phase space. The integrator works with vector quantities:

X(t+dt) = X(t) + V(t) dt + (1/2) A(t) dt2

V(t+dt) = V(t) + (1/2)(A(t) + A(t+dt)) dt

Here X(t) is a column-vector that consists of the coordinates of all particles - this is the configuration subspace of the system's phase space. V(t) is a column-vector of the speeds of all particles, technically representing the momentum subspace. The same applies to A(t). Those have to updated simultaneously, not separately.

The whole velocity Verlet procedure translates as follows in code for force fields that do not depend on the speed (e.g. classical gravity):

Vector forces[num_particles];

// Compute initial forces
forces = computeForces(positions);

for (int ts = 0; ts < num_timesteps; ts++)
{
   // Update positions and half-update velocities
   for (int i = 0; i < num_particles; i++)
   {
      positions[i] += velocities[i]*dt + 0.5*(forces[i] / m[i]) * dt*dt;
      velocities[i] += 0.5*(forces[i] / m[i]) * dt;
   }

   // Compute new forces and half-update velocities
   forces = computeForces(positions);

   for (int i = 0; i < num_particles; i++)
   {
      velocities[i] += 0.5*(forces[i] / m[i]) * dt;
   }
}

Note that all positions are updated first before the next round of force evaluation. Also it is only necessary to evaluate the forces once per iteration since the positions do not change during the second update to the velocities. In the example code above Vector is a class that implements an n-dimensional vector and holds n components (e.g. 2 in your 2d-case). It also overloads the + and += operators to implement vector (component-wise) addition, as well as * and / to implement multiplication / division by a scalar. This is just to illustrate the case and can be replaced by inner loops over the components of each position/velocity vector.

The correct choice of time step is very important. Too small time step will slow down the simulation significantly. Too large time step will result in impossible physics, e.g. jumping planets.

There are some problems with the physics and some problems with the code.

First, the problem with the physics. Assuming that we're not modelling an alternative universe where the laws of physics are different, Newton's law of universal gravitation says that F=G*m1*m2/(r*r). However, force is a vector, not a scalar, so it has both magnitude and direction.

What the code calculates in forceFuncX is actually the magnitude rather than merely the component of the force parallel to the X axis. forceFuncY has the same flaw.

Next is the calculation for acceleration. Physics says that this is a=F/m. Mass is a scalar but both acceleration and force are vectors. So to calculate a_x, we could either use F_x/m or we could calculate F*cos(a)/m. Because cos(a) (with a being the angle from one CelesitalObject to another in 2D space) = dx/r, we can make this a_x = F*dx/(m*r) which is almost but not quite what you've got in your calculation (it's missing r in the divisor).

An alternative approach is to use std::complex but I'll not show that approach with the assumption that you may wish to extend this model to three dimensions.

That brings us to problems with the code. First, since you're using C++ and writing a simulation of a physical system of discrete objects, it makes sense that you've defined a CelestialObject class. What makes less sense is that your functions are called by picking out individual pieces of those objects and then calling C-style functions. We can improve the code by using those objects better. To start, since you haven't posted one, here's a CelestialObject class based on the interface I inferred from your code:

class CelestialObject 
{
public:
    CelestialObject(std::string name, float mass, float X, float Y, 
        float VX, float VY)
            : myname(name), m(mass), x(X), y(Y), vx(VX), vy(VY) {}
    void setPosition(float X, float Y) { x=X; y=Y; }
    void setVelocity(float VX, float VY) { vx=VX; vy=VY; }
    float getMass() const { return m; } 
    float getX() const { return x; } 
    float getY() const { return y; } 
    float getVX() const { return vx; } 
    float getVY() const { return vy; } 
    friend std::ostream& operator<<(std::ostream& out, 
                                    const CelestialObject& obj) {
        return out << obj.myname << '\t' << obj.x << '\t' << obj.y 
                   << '\t' << obj.vx << '\t' << obj.vy << std::endl;
    }
private:
    std::string myname;
    float m, x, y;
    float vx, vy;
};

Next, a few helper functions:

// returns square of distance between objects
float distance_sq(const CelestialObject &a, const CelestialObject &b)
{
    // distance squared is (dy^2 + dx^2)
    return pow(a.getY()-b.getY(),2) + pow(a.getX()-b.getX(),2);
}

// returns magnitude of the force between the objects
float force(const CelestialObject &a, const CelestialObject &b)
{
    //  F=(G * m1 * m1)/(r^2) in the direction a->b and b->a
    return G*a.getMass()*b.getMass()/distance_sq(a, b);
}

// returns the angle from a to b
float angle(const CelestialObject &a, const CelestialObject &b)
{
    return atan2f(b.getY()-a.getY(),b.getX()-a.getX());
}

Finally the actual verlet:

void updatePosition(CelestialObject &a, CelestialObject &b )
{ 
    float F = force(a,b);
    float theta = angle(a,b);
    float accela = F/a.getMass();
    float accelb = -F/b.getMass();

    // now that we have the acceleration of both objects, update positions
    // x = x +v *dt + a*dt*dt/2
    //   = x + dt * (v + a*dt/2)
    a.setPosition(
     a.getX() + dt * (a.getVX() + accela*cos(theta)*dt/2),
     a.getY() + dt * (a.getVY() + accela*sin(theta)*dt/2) 
    );
    b.setPosition(
     b.getX() + dt * (b.getVX() + accelb*cos(theta)*dt/2),
     b.getY() + dt * (b.getVY() + accelb*sin(theta)*dt/2)
    );
    // get new acceleration a'
    F = force(a,b);
    float thetap = angle(a,b);
    float accelap = F/a.getMass();
    float accelbp = -F/b.getMass();
    // and update velocities
    // v = v + (a + a')*dt/2
    a.setVelocity(
     a.getVX() + (accela*cos(theta) + accelap*cos(thetap))*dt/2,
     a.getVY() + (accela*sin(theta) + accelap*sin(thetap))*dt/2
    );
    b.setVelocity(
     b.getVX() + (accelb*cos(theta) + accelbp*cos(thetap))*dt/2,
     b.getVY() + (accelb*sin(theta) + accelbp*sin(thetap))*dt/2
    );
}

And finally some simple test code.

#include <string>
#include <iostream>
#include <vector>
#include <cmath>

const float G(6.67e-11);  // N*(m/kg)^2
const float dt(0.1);      // s
// all of the other code goes here...
int main()
{
    CelestialObject anvil("anvil", 70, 370, 0, 0, 0);
    CelestialObject earth("earth", 5.97e+24, -6.378e6, 0, 0, 0);
    std::cout << "Initial values:\n" << earth << anvil;
    std::cout << "Dropping an anvil from the top of a 370m building...\n"
              "It should hit the ground in about 8.7 seconds.\n";
    int t;
    for (t=0; anvil.getX() > 0; ++t) {
    std::cout << dt*t << '\t' << anvil; 
    updatePosition(anvil, earth);
    }
    std::cout << "Final values at t = " << dt*t << " seconds:\n" 
              << earth << anvil;
    return 0;
}

The test code uses a time step of 0.1s which is far too short for your solar system but fine for this quick test which is to see if we get a reasonable result for a known system. In this case, I've chosen a two-body system consisting of the planet Earth and an anvil. This code simulates dropping an anvil from the top of a 370m building which we can easily calculate will hit the ground in about 8.7s if we neglect air resistance. To keep the coordinates simple, I chose to place the origin (0,0) at the surface of the earth, and to consider the top of the building to be at (370,0). When the code is compiled and run, it produces the following:

Initial values:
earth   -6.378e+06  0   0   0
anvil   370 0   0   0
Dropping an anvil from the top of a 370m building...
It should hit the ground in about 8.7 seconds.
0   anvil   370 0   0   0
0.1 anvil   369.951 -4.27834e-09    -0.97877    -8.55668e-08
0.2 anvil   369.804 -1.71134e-08    -1.95754    -1.71134e-07
0.3 anvil   369.56  -3.85051e-08    -2.93631    -2.567e-07
   ...
8.3 anvil   32.8567 -2.9474e-05 -81.2408    -7.1023e-06
8.4 anvil   24.6837 -3.01885e-05    -82.2197    -7.18787e-06
8.5 anvil   16.4127 -3.09116e-05    -83.1985    -7.27345e-06
8.6 anvil   8.04394 -3.16432e-05    -84.1774    -7.35902e-06
Final values at t = 8.7 seconds:
earth   -6.378e+06  3.79705e-28 9.98483e-22 8.72901e-29
anvil   -0.422744   -3.23834e-05    -85.1563    -7.4446e-06

As you can see, this seems to work, but there are problems. The first problem is that since the objects should only move along the X axis, all Y components should be 0. They are not because this code is not very well-designed from a numerical analysis point of view. Doing additions and subtractions of floating point numbers when one number is large and another is small is one problem. Another is the use of the atan2f function which returns only a float and then using that result in cos() and sin(). Trigonometric functions are actually best avoided if possible.

Finally, this program currently only works with two objects. Adding a third would be painful with this kind of scheme, so a better design would be to process a std::vector<CelestialObject> by first calculating the net force on each object by considering the positions and masses of all of the others. I'll leave that to you, but this should at least give you a start in the right direction.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!