从状态向量中找出真正的异常

时间:2019-05-26 22:45:22

标签: c# math orbital-mechanics

我正在尝试从状态向量(位置和速度)转换为开普勒元素,但是我遇到了这样的问题:在尝试计算真实异常时,负速度或位置会给我错误的结果。

以下是我尝试计算真实异常的不同方法:

/// <summary>
/// https://en.wikipedia.org/wiki/True_anomaly#From_state_vectors
/// </summary>
public static double TrueAnomaly(Vector4 eccentVector, Vector4 position, Vector4 velocity)
{
    var dotEccPos = Vector4.Dot(eccentVector, position);
    var talen = eccentVector.Length() * position.Length();
    talen = dotEccPos / talen;
    talen = GMath.Clamp(talen, -1, 1);
    var trueAnomoly = Math.Acos(talen);

    if (Vector4.Dot(position, velocity) < 0)
        trueAnomoly = Math.PI * 2 - trueAnomoly;

    return trueAnomoly;
}
//sgp = standard gravitational parameter
public static double TrueAnomaly(double sgp, Vector4 position, Vector4 velocity)
{
    var H = Vector4.Cross(position, velocity).Length();
    var R = position.Length();
    var q = Vector4.Dot(position, velocity);  // dot product of r*v
    var TAx = H * H / (R * sgp) - 1;
    var TAy = H * q / (R * sgp);
    var TA = Math.Atan2(TAy, TAx);
    return TA;
}
public static double TrueAnomalyFromEccentricAnomaly(double eccentricity, double eccentricAnomaly)
{
    var x = Math.Sqrt(1 - Math.Pow(eccentricity, 2)) * Math.Sin(eccentricAnomaly);
    var y = Math.Cos(eccentricAnomaly) - eccentricity;
    return Math.Atan2(x, y);
}
public static double TrueAnomalyFromEccentricAnomaly2(double eccentricity, double eccentricAnomaly)
{
    var x = Math.Cos(eccentricAnomaly) - eccentricity;
    var y = 1 - eccentricity * Math.Cos(eccentricAnomaly);
    return Math.Acos(x / y);
}

编辑:幽灵指出的另一种处理方式:

public static double TrueAnomaly(Vector4 position, double loP)
{
    return Math.Atan2(position.Y, position.X) - loP; 
}

位置都相对于父实体。

如果position.x,position.y和velocity.y均为正数,则这些函数都一致。 如何解决这些问题,以便在位置和速度为负值时获得一致的结果?

只需澄清一下:我的角度似乎是正确的,只是根据位置和/或速度矢量指向错误的象限。

是的,所以我错了,毕竟所有这些确实返回正确的值。

因此,我发现上述大多数计算都失败的极端情况。 给定位置和速度:

pos = new Vector4() { X = -0.208994076275941, Y = 0.955838328099748 };
vel = new Vector4() { X = -2.1678187689294E-07, Y = -7.93096769486992E-08 };

我得到一些奇怪的结果,即〜-31.1度,当我认为时,它应该返回31.1(非负)。其中之一返回〜328.8。
但是,使用此位置和速度进行测试,结果似乎还可以:

pos = new Vector4() { X = -0.25, Y = 0.25 };
vel = new Vector4() { X = Distance.KmToAU(-25), Y = Distance.KmToAU(-25) };

请参阅我的答案以获取有关我如何测试以及其他一些变量使用的数学信息的额外代码。

我要绕这圈转。这是由于我现有代码中的错误在某些情况下而不是其他情况下出现的结果。 我想现在真正的问题是为什么我在位置/速度上方得到的结果是否与我的期望不符或彼此不符?

3 个答案:

答案 0 :(得分:1)

我假设您是在两个维度上工作?

位置p和速度v的二维向量。常数K是重力常数与重力产生体质量的乘积。计算偏心向量

eccVector = (dot(v, v)*p - dot(v, p)*v) / K - p / sqrt(dot(p, p));

eccentricity = sqrt(dot(eccVector, eccVector));

eccVector = eccVector / eccentricity;

b = { - eccVector.y, eccVector.x};  //unit vector perpendicular to eccVector  

r = sqrt(dot(p, p));

cos_TA = dot(p, eccVector) / r;   \\ cosine of true anomaly
sin_TA = dot(p, b) / r;           \\ sine of true anomaly

if (sin_TA >= 0) { 
   trueAnomaly = arccos(cos_TA);
}
else if (sin_TA < 0){
   trueAnomaly = 2*pi - arccos(cos_TA);
}

答案 1 :(得分:1)

假设是2D情况...我的做法有所不同:

  1. 计算半轴半径和旋转

    因此,您需要记住整个轨道并在其上找到2个最远的点,即主轴a。短轴b通常与长轴成90度,但是请确保仅在轨道上将2个垂直最远点指向长轴。所以现在您有了两个半轴。初始旋转是通过atan2从主轴算出的。

  2. 计算真实异常E

    因此,如果中心为x0,y0a,b的交点或两者的中心点)的初始旋转为ang0(角度为a),并且您在轨道上的点为{ {1}}然后:

    x,y

但是,为了使牛顿/达朗贝尔物理学与开普勒轨道参数匹配,您需要像我在这里那样提高积分精度:

请参见 [Edit3]进一步提高Newton D'ALembert集成精度

有关更多信息和方程式,请参见:

[Edit1],因此您想计算E = atan2(y-y0,x-x0) - ang0 ,我会这样看:

img

当您获得相对于父级的坐标时,可以假定它们已经在焦点中心,因此不再需要V。粗略地讲,如果您想要高精度并且具有两个以上的物体(焦点质量+物体+卫星等邻近物体),则父质量将不再位于轨道焦点上,而是靠近轨道...并进行补救您需要使用真实的焦点位置,所以再次使用x0,y0 ...那么如何操作:

  1. 计算中心点x0,y0和a,b半轴

    因此与之前的文字相同。

  2. 计算轨道轴对齐坐标中的焦点(cx,cy)

    简单:

    (x0,y0)
  3. x0 = cx + sqrt( a^2 + b^2 ); y0 = cy; 的初始角度ang0

    a是轨道和主轴xa,ya在速度较大(靠近父对象焦点)一侧的交点。然后:

    a
  4. ,最后ang0 = atan2( ya-cy , xa-cx ); 出现在您任何V

    之前
    x,y

答案 2 :(得分:1)

好,所以在进一步测试中,似乎我的原始计算都返回了正确的值,但是当我查看输出时,我没有考虑LoP,并且基本上没有意识到180°基本上是相同的角度为-180。 (我也在看弧度的输出,只是没有看到什么应该是显而易见的) 长话短说,我有一个我认为是在该代码区域中的错误,但在杂草中迷路了。
上面我似乎是错的。有关边缘情况,请参见OP。
这是我用来测试这些代码的一些代码,
我使用了以下输入的变体:

pos = new Vector4() { X = 0.25, Y = 0.25 };
vel = new Vector4() { X = Distance.KmToAU(-25), Y = Distance.KmToAU(25) };

并使用以下内容对其进行了测试

double parentMass = 1.989e30;
double objMass = 2.2e+15;
double sgp = GameConstants.Science.GravitationalConstant * (parentMass + objMass) / 3.347928976e33;

Vector4 ev = OrbitMath.EccentricityVector(sgp, pos, vel);
double e = ev.Length();
double specificOrbitalEnergy = Math.Pow(vel.Length(), 2) * 0.5 - sgp / pos.Length();
double a = -sgp / (2 * specificOrbitalEnergy);
double ae = e * a;
double aop = Math.Atan2(ev.Y, ev.X);
double eccentricAnomaly = OrbitMath.GetEccentricAnomalyFromStateVectors(pos, a, ae, aop);
double aopD = Angle.ToDegrees(aop);
double directAngle = Math.Atan2(pos.Y, pos.X);

var θ1 = OrbitMath.TrueAnomaly(sgp, pos, vel);
var θ2 = OrbitMath.TrueAnomaly(ev, pos, vel);
var θ3 = OrbitMath.TrueAnomalyFromEccentricAnomaly(e, eccentricAnomaly);
var θ4 = OrbitMath.TrueAnomalyFromEccentricAnomaly2(e, eccentricAnomaly);
var θ5 = OrbitMath.TrueAnomaly(pos, aop);
double angleΔ = 0.0000001; //this is the "acceptable" amount of error, really only the TrueAnomalyFromEccentricAnomaly() calcs needed this. 
Assert.AreEqual(0, Angle.DifferenceBetweenRadians(directAngle, aop - θ1), angleΔ);
Assert.AreEqual(0, Angle.DifferenceBetweenRadians(directAngle, aop - θ2), angleΔ);
Assert.AreEqual(0, Angle.DifferenceBetweenRadians(directAngle, aop - θ3), angleΔ);
Assert.AreEqual(0, Angle.DifferenceBetweenRadians(directAngle, aop - θ4), angleΔ);
Assert.AreEqual(0, Angle.DifferenceBetweenRadians(directAngle, aop - θ5), angleΔ);

和以下内容比较角度:

public static double DifferenceBetweenRadians(double a1, double a2)
{
    return Math.PI - Math.Abs(Math.Abs(a1 - a2) - Math.PI);
}

发现偏心向量:

public static Vector4 EccentricityVector(double sgp, Vector4 position, Vector4 velocity)
{
    Vector4 angularMomentum = Vector4.Cross(position, velocity);
    Vector4 foo1 = Vector4.Cross(velocity, angularMomentum) / sgp;
    var foo2 = position / position.Length();
    return foo1 - foo2;
}

和异常异常:

public static double GetEccentricAnomalyFromStateVectors(Vector4 position, double a, double linierEccentricity, double aop)
{
    var x = (position.X * Math.Cos(-aop)) - (position.Y * Math.Sin(-aop));
    x = linierEccentricity + x;
    double foo = GMath.Clamp(x / a, -1, 1); //because sometimes we were getting a floating point error that resulted in numbers infinatly smaller than -1
    return Math.Acos(foo);
}

感谢Futurogogist和Spektre的帮助。