对于我的建模和模拟课程项目,我想模拟一个太阳系。我只是从一颗恒星(太阳)和一颗行星(地球)开始,但我已经遇到了一些问题。我现在花了一些时间来回顾和了解不同的公式以及模拟星球和周围物体如何影响行星轨道的方法。我想使用速度verlet并最终调查n体问题。我的速度verlet函数存在很多问题。有时它的行为就好像它正常地制造地球轨道一样然后它就会发生。变形驱动器"接地到一些随机位置。我也注意到我从来没有得到一个消极的"加速,所以我的x和y坐标。一直在增加,所以我不知道地球是如何围绕太阳回绕的。任何帮助是极大的赞赏。 AGK :: Prints我刚才可以看到不同的变量是如何变化的。
double velocityVerlet(float positionCalc, double position2,
float &velocity, double massCalc, double mass2)
//positionCalc is the position being updated, position 2 is position of
// other object, same with mass
{
float force = forceFunc(positionCalc, position2, massCalc, mass2);
agk::PrintC("Force is: ");
agk::Print(force);
float acceleration = accelerationFunc(force,massCalc);
agk::PrintC("Accel is: ");
agk::Print(acceleration);`;
double newAccel = 0;
positionCalc = positionCalc + velocity*dt +
(.5*acceleration)*pow(dt,2); //calculates new position
agk::PrintC("New Position is: ");
agk::Print(positionCalc);
force = forceFunc(positionCalc,position2,massCalc,mass2);
newAccel = accelerationFunc(force, massCalc);
velocity = velocity + .5*(acceleration + newAccel)*dt; //new velocity
agk::PrintC("Velocity is: ");
agk::Print(velocity);
return positionCalc;
}
答案 0 :(得分:3)
您的集成商接受标量的事实以及您的问题是关于二维系统的事实使我认为您正在调用集成商两次,每个组件一次。由于您的系统将在相位空间中采取不切实际的行动,因此这简单地无法工作。积分器使用矢量:
X (t + dt)= X (t)+ V (t)dt +(1/2) A (t)dt 2
V (t + dt)= V (t)+(1/2)( A (t)+ A (t + dt))dt
这里 X (t)是一个列向量,由所有粒子的坐标组成 - 这是系统相位空间的配置子空间。 V (t)是所有粒子速度的列向量,技术上代表动量子空间。这同样适用于 A (t)。那些必须同时更新 ,而不是单独更新。
整个速度Verlet程序在不依赖于速度的力场(例如经典重力)的代码中转换如下:
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;
}
}
请注意,所有职位在下一轮评估之前都会先更新。此外,每次迭代仅需要评估一次力,因为在第二次更新速度期间位置不会改变。在上面的示例代码中,Vector
是一个实现n维向量并保存n
组件的类(例如,在2d情况下为2)。它还会使+
和+=
运算符重载以实现向量(分量)加法,以及*
和/
以通过标量实现乘法/除法。这只是为了说明这种情况,可以用每个位置/速度矢量的组件上的内环代替。
正确选择时间步骤非常重要。太小的时间步长将显着减慢模拟速度。太大的时间步长将导致不可能的物理,例如跳行星。
答案 1 :(得分:2)
物理问题和代码存在一些问题。
首先,物理问题。假设我们没有为物理定律不同的另类宇宙建模,牛顿万有引力定律表明F = G * m1 * m2 /(r * r)。然而,力是一个向量,而不是一个标量,所以它既有大小又有方向。
代码在forceFuncX
中计算的实际上是幅度,而不仅仅是平行于X轴的力的分量。 forceFuncY
也有同样的缺陷。
接下来是加速度的计算。物理学说这是a = F / m。质量是一个标量,但加速度和力都是矢量。所以为了计算a_x,我们可以使用F_x / m或者我们可以计算F * cos( a )/ m。因为cos( a )( a 是在2D空间中从一个CelesitalObject
到另一个的角度)= dx / r,我们可以使这个a_x = F * dx /(m * r)这几乎不是你计算中得到的(它在除数中缺少r)。
另一种方法是使用std::complex
,但我并未假设您希望将此模型扩展到三维,而不会显示该方法。
这让我们遇到了代码问题。首先,由于您正在使用C ++并编写离散对象物理系统的模拟,因此您定义了CelestialObject
类是有意义的。更没意义的是,通过挑选这些对象的各个部分然后调用C风格的函数来调用函数。我们可以通过更好地使用这些对象来改进代码。首先,由于您还没有发布一个,所以根据我从您的代码中推断出的界面,这里有一个CelestialObject
类:
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;
};
接下来,一些辅助函数:
// 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());
}
最后是实际的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
);
}
最后是一些简单的测试代码。
#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;
}
测试代码使用0.1秒的时间步长,这对于您的太阳系来说太短了,但对于这个快速测试来说很好,这是为了看看我们是否为已知系统得到了合理的结果。在这种情况下,我选择了一个由地球和铁砧组成的双体系统。这个代码模拟从370米建筑物的顶部掉落一个铁砧,如果我们忽略空气阻力,我们可以很容易地计算出它将在大约8.7秒内撞击地面。为了保持坐标简单,我选择将原点(0,0)放置在地球表面,并将建筑物的顶部视为(370,0)。编译并运行代码时,会生成以下内容:
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
正如您所看到的,这似乎有效,但存在问题。第一个问题是,由于对象只应沿X轴移动,因此所有Y分量都应为0.它们不是因为从数值分析的角度来看,这个代码设计得不是很好。当一个数字很大而另一个数字很小时,对浮点数进行加法和减法是一个问题。另一个是使用atan2f
函数,该函数仅返回float
,然后在cos()
和sin()
中使用该结果。如果可能,实际上最好避免使用三角函数。
最后,该程序目前仅适用于两个对象。添加第三个对于这种方案会很痛苦,因此更好的设计是通过首先通过考虑所有对象的位置和质量来计算每个对象上的净力来处理std::vector<CelestialObject>
。其他人。我会把它留给你,但这至少应该给你一个正确的方向。