你犯了两个明显的错误和一个糟糕的算法选择。
首先,您已将月球的位置建模为单个数字而不是 3 空间中的矢量,因此您在此处建模的是简单的谐波运动,例如受限于单一维度的弹簧,而不是轨道。首先将位置和速度建模为 3 空间向量,而不是两倍。
其次,你把万有引力的符号设为正,所以它是两个物体之间的排斥力,而不是吸引力。力的符号必须在地球的方向。
第三,您对欧拉算法的实现似乎是正确的,但欧拉在数值求解轨道问题时是一个糟糕的选择,因为它不保守;你很容易陷入月球获得或失去一点动量的情况,这些情况加起来会破坏你漂亮的椭圆轨道。
由于月球的轨道是哈密顿轨道,因此请改用辛算法;它旨在模拟保守系统。
https://en.wikipedia.org/wiki/Symplectic_integrator
这种方法和您的欧拉方法基本上是关于找到状态向量:
https://en.wikipedia.org/wiki/Orbital_state_vectors
但是,对于您的简单 2 体系统,有更简单的方法。如果你想做一个像 Kerbal Space Program 这样的模拟,只有你在轨道上运行的物体会影响你的轨道,并且有多个卫星的行星“在轨道上”,你不需要在每个时间单位上模拟系统计算出状态向量的序列;您可以在给定开普勒元素的情况下直接计算它们:
https://en.wikipedia.org/wiki/Orbital_elements
我们非常精确地了解月球的六种元素;从那些你可以随时计算月球在 3 空间中的位置,再次假设没有任何东西干扰它的轨道。(实际上,月球的轨道会因太阳、地球不是球体这一事实、潮汐等因素而改变。)
更新:
首先,由于这是课程作业,您需要引用所有资源,包括从互联网获得帮助。你的导师必须知道你的工作是什么,以及你让别人为你做的工作。
您问如何在两个维度上完成这项工作;这似乎是错误的,但无论如何,按照课程作业所说的去做。
我希望更多的初学者被教导的规则是:制作一个类型、一个方法或一个变量来解决你的问题。在这种情况下,我们希望表示一个复杂值的行为,所以它应该是一个类型,而那个类型应该是一个值类型。值类型struct
在 C# 中。所以让我们这样做。
struct Vector2
{
double X { get; }
double Y { get; }
public Vector2(double x, double y)
{
this.X = x;
this.Y = y;
}
请注意,向量是不可变的,就像数字一样。你永远不会改变 vector。当你需要一个新的向量时,你就创建一个新的。
我们需要对向量执行哪些操作?向量加法、标量乘法和标量除法只是花式乘法。让我们实现这些:
public static Vector2 operator +(Vector2 a, Vector2 b) =>
new Vector2(a.X + b.X, a.Y + b.Y);
public static Vector2 operator -(Vector2 a, Vector2 b) =>
new Vector2(a.X - b.X, a.Y - b.Y);
public static Vector2 operator *(Vector2 a, double b) =>
new Vector2(a.X * b, a.Y * b);
public static Vector2 operator /(Vector2 a, double b) =>
a * (1.0 / b);
我们也可以按其他顺序进行乘法运算,所以让我们实现它:
public static Vector2 operator *(double b, Vector2 a) =>
a * b;
将向量设为负数与将其乘以 -1 相同:
public static Vector2 operator -(Vector2 a) => a * -1.0;
并帮助我们调试:
public override string ToString() => $"({this.X},{this.Y})";
}
我们已经完成了向量。
我们正在尝试模拟轨道状态参数的演变,所以再次制作一个 type。什么是状态参数?位置和速度:
struct State
{
Vector2 Position { get; }
Vector2 Velocity { get; }
public State(Vector2 position, Vector2 velocity)
{
this.Position = position;
this.Velocity = velocity;
}
现在我们进入核心算法。我们有什么?一个状态和一个加速度。我们想要什么?一个新的状态。做一个方法:
public State Euler(Vector2 acceleration, double step)
{
Vector2 newVelocity = this.Velocity + acceleration * step;
Vector2 newPosition = this.Position + this.Velocity * step;
return new State(newPosition, newVelocity);
}
}
极好的。还剩下什么? 我们需要计算出加速度。做一个方法:
static Vector2 AccelerationOfTheMoon(Vector2 position)
{
// You do this. Acceleration is force divided by mass,
// force is a vector, mass is a scalar. What is the force
// given the position? DO NOT MAKE A SIGN MISTAKE AGAIN.
}
现在您拥有所需的所有部件。从初始状态开始,您可以反复调用 AccelerationOfTheMoon 来获取新的加速度,然后调用 Euler 来获取新的状态,然后重复。
作为初学者,请仔细研究这些技巧。请注意我所做的:我创建了表示概念的类型,以及表示对这些概念的操作的方法。一旦你这样做了,程序就会变得非常清晰易读。
想想你将如何使用这些技术来扩展这个程序;我们做了一个硬编码的AccelerationOfTheMoon
方法,但是如果我们想计算几个物体的加速度怎么办?再次,创建一个类型来表示Body
;身体的特点是State
质量。如果我们想解决 n 体问题怎么办?我们的加速度计算必须将其他物体作为参数。等等。