diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics.CLI/IO.Astrodynamics.CLI.csproj b/IO.Astrodynamics.Net/IO.Astrodynamics.CLI/IO.Astrodynamics.CLI.csproj index 36b9ad97..f928d93c 100644 --- a/IO.Astrodynamics.Net/IO.Astrodynamics.CLI/IO.Astrodynamics.CLI.csproj +++ b/IO.Astrodynamics.Net/IO.Astrodynamics.CLI/IO.Astrodynamics.CLI.csproj @@ -9,7 +9,7 @@ 0.0.1 true astro - 0.6.2.1 + 0.6.3.1 Astrodynamics command line interface Sylvain Guillet This CLI allows end user to exploit IO.Astrodynamics framework diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics.Tests/OrbitalParameters/FindOrbitalParametersFromObservations.cs b/IO.Astrodynamics.Net/IO.Astrodynamics.Tests/OrbitalParameters/FindOrbitalParametersFromObservations.cs new file mode 100644 index 00000000..adc2508b --- /dev/null +++ b/IO.Astrodynamics.Net/IO.Astrodynamics.Tests/OrbitalParameters/FindOrbitalParametersFromObservations.cs @@ -0,0 +1,101 @@ +using System; +using IO.Astrodynamics.Body; +using IO.Astrodynamics.Coordinates; +using IO.Astrodynamics.Math; +using IO.Astrodynamics.OrbitalParameters; +using IO.Astrodynamics.SolarSystemObjects; +using IO.Astrodynamics.Surface; +using Xunit; + +namespace IO.Astrodynamics.Tests.OrbitalParameters; + +public class InitialOrbitDeterminationTests +{ + public InitialOrbitDeterminationTests() + { + API.Instance.LoadKernels(Constants.SolarSystemKernelPath); + } + + [Fact] + public void GeosynchronousObject() + { + for (int i = 1; i <= 5; i++) + { + var timespan = TimeSpan.FromMinutes(i * 2); + Site site = new Site(80, "MyStation", TestHelpers.EarthAtJ2000, new Planetodetic(0.0, 45.0 * Constants.DEG_RAD, 0.0)); + + var e2 = new TimeSystem.Time(2024, 1, 2); + + var e1 = e2 - timespan; + var e3 = e2 + timespan; + var referenceOrbit = new KeplerianElements( + semiMajorAxis: 42164000.0, // m + eccentricity: 0.0, + inclination: 15.0 * Constants.DEG_RAD, + rigthAscendingNode: 45.0 * Constants.DEG_RAD, + argumentOfPeriapsis: 0.0, + meanAnomaly: 45.0 * Constants.DEG_RAD, // Décalage de 45 degrés + observer: TestHelpers.EarthAtJ2000, + frame: Frames.Frame.ICRF, + epoch: e2 + ); + var obs1 = referenceOrbit.AtEpoch(e1).RelativeTo(site, Aberration.LT); + var obs2 = referenceOrbit.AtEpoch(e2).RelativeTo(site, Aberration.LT); + var obs3 = referenceOrbit.AtEpoch(e3).RelativeTo(site, Aberration.LT); + var orbitalParams = + InitialOrbitDetermination.CreateFromObservation_Gauss(obs1.ToEquatorial(), obs2.ToEquatorial(), obs3.ToEquatorial(), site, + PlanetsAndMoons.EARTH_BODY, 42000000.0); + var deltaRange = 100.0 * (orbitalParams.OrbitalParameters.ToStateVector().Position - referenceOrbit.ToStateVector().Position).Magnitude() / + referenceOrbit.ToStateVector().Position.Magnitude(); + var deltaVelocity = 100.0 * (orbitalParams.OrbitalParameters.ToStateVector().Velocity - referenceOrbit.ToStateVector().Velocity).Magnitude() / + referenceOrbit.ToStateVector().Velocity.Magnitude(); + Assert.True(deltaRange < 0.02); + Assert.True(deltaVelocity < 0.02); + } + } + + [Fact] + public void PlanetObject() + { + var timespan = TimeSpan.FromDays(10); + Site site = new Site(13, "DSS-13", TestHelpers.EarthAtJ2000); + + var e2 = new TimeSystem.Time(2023, 3, 1); + var e1 = e2 - timespan; + var e3 = e2 + timespan; + var target = new Barycenter(4); + var obs1 = target.GetEphemeris(e1, site, Frames.Frame.ICRF, Aberration.LT); + var obs2 = target.GetEphemeris(e2, site, Frames.Frame.ICRF, Aberration.LT); + var obs3 = target.GetEphemeris(e3, site, Frames.Frame.ICRF, Aberration.LT); + var referenceOrbit = target.GetEphemeris(e2, TestHelpers.Sun, Frames.Frame.ICRF, Aberration.LT); + var orbitalParams = + InitialOrbitDetermination.CreateFromObservation_Gauss(obs1.ToEquatorial(), obs2.ToEquatorial(), obs3.ToEquatorial(), site, + Stars.SUN_BODY, 350000000000.58063); + + var deltaRange = 100.0 * (orbitalParams.OrbitalParameters.ToStateVector().Position - referenceOrbit.ToStateVector().Position).Magnitude() / + referenceOrbit.ToStateVector().Position.Magnitude(); + var deltaVelocity = 100.0 * (orbitalParams.OrbitalParameters.ToStateVector().Velocity - referenceOrbit.ToStateVector().Velocity).Magnitude() / + referenceOrbit.ToStateVector().Velocity.Magnitude(); + + Assert.True(deltaRange < 0.06); + Assert.True(deltaVelocity < 0.15); + } + + [Fact] + public void COBE() + { + var ut1 = new TimeSystem.Time(2000, 11, 6, 22, 31, 29, 0, 0, TimeSystem.TimeFrame.UTCFrame); + var ut2 = new TimeSystem.Time(2000, 11, 6, 22, 34, 30, 0, 0, TimeSystem.TimeFrame.UTCFrame); + var ut3 = new TimeSystem.Time(2000, 11, 6, 22, 37, 30, 0, 0, TimeSystem.TimeFrame.UTCFrame); + var site = new Site(99, "Maryland university", TestHelpers.EarthAtJ2000, new Planetodetic(-76.95667 * Constants.DEG_RAD, 39.00167 * Constants.DEG_RAD, 53.0)); + var obs1 = new Equatorial(-16.3 * Astrodynamics.Constants.Deg2Rad, 327.0 * Astrodynamics.Constants.Deg2Rad, 7250000.0, ut1); + var obs2 = new Equatorial(46.9 * Astrodynamics.Constants.Deg2Rad, 318.5 * Astrodynamics.Constants.Deg2Rad, 7250000.0, ut2); + var obs3 = new Equatorial(76.1 * Astrodynamics.Constants.Deg2Rad, 165.75 * Astrodynamics.Constants.Deg2Rad, 7250000.0, ut3); + var orbitalParams = + InitialOrbitDetermination.CreateFromObservation_Gauss(obs1, obs2, obs3, site, PlanetsAndMoons.EARTH_BODY, 7_250_000.0); + StateVector referenceOrbit = new(new Vector3(3520477.0118993367, -4310404.2221997585, 4645118.5764311915), + new Vector3(-4026.2486947722973, 2615.67401249017, 5468.113004203227), + TestHelpers.EarthAtJ2000, ut2, Frames.Frame.ICRF); + Assert.Equal(referenceOrbit, orbitalParams.OrbitalParameters.ToStateVector(), TestHelpers.StateVectorComparer); + } +} \ No newline at end of file diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/Coordinates/Equatorial.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/Coordinates/Equatorial.cs index c8f779a3..aac976d8 100644 --- a/IO.Astrodynamics.Net/IO.Astrodynamics/Coordinates/Equatorial.cs +++ b/IO.Astrodynamics.Net/IO.Astrodynamics/Coordinates/Equatorial.cs @@ -47,5 +47,13 @@ public Vector3 ToCartesian() double z = Distance * System.Math.Sin(Declination); return new Vector3(x, y, z); } + + public Vector3 ToDirection() + { + double x = System.Math.Cos(Declination) * System.Math.Cos(RightAscension); + double y = System.Math.Cos(Declination) * System.Math.Sin(RightAscension); + double z = System.Math.Sin(Declination); + return new Vector3(x, y, z); + } } } \ No newline at end of file diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/IO.Astrodynamics.nuspec b/IO.Astrodynamics.Net/IO.Astrodynamics/IO.Astrodynamics.nuspec index c45c24e4..2e53cc16 100644 --- a/IO.Astrodynamics.Net/IO.Astrodynamics/IO.Astrodynamics.nuspec +++ b/IO.Astrodynamics.Net/IO.Astrodynamics/IO.Astrodynamics.nuspec @@ -4,7 +4,7 @@ IO.Astrodynamics Sylvain Guillet Sylvain Guillet - 6.2.0 + 6.3.0 Astrodynamics framework images\dragonfly-dark-trans.png docs\README.md diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Bissection.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Bissection.cs new file mode 100644 index 00000000..d02f8721 --- /dev/null +++ b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Bissection.cs @@ -0,0 +1,47 @@ +using System; + +public class BisectionMethod +{ + public static double Solve( + Func f, // Your polynomial function + double a, // Left endpoint + double b, // Right endpoint + double tolerance = 1E-12, // Matching your tolerance + int maxIterations = 1000) // Matching your max iterations + { + if (f(a) * f(b) >= 0) + { + throw new ArgumentException("Function must have different signs at endpoints a and b"); + } + + double c = a; + int iterations = 0; + + while ((b - a) > tolerance && iterations < maxIterations) + { + c = (a + b) / 2; + double fc = f(c); + + if (Math.Abs(fc) < tolerance) + break; + + if (f(a) * fc < 0) + { + b = c; + } + else + { + a = c; + } + + iterations++; + } + + if (iterations >= maxIterations) + { + throw new ArgumentException($"Failed to converge after {maxIterations} iterations"); + } + + return c; + } +} \ No newline at end of file diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/Math/NewtonRaphson.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/NewtonRaphson.cs new file mode 100644 index 00000000..fb7b33de --- /dev/null +++ b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/NewtonRaphson.cs @@ -0,0 +1,85 @@ +using System; + +namespace IO.Astrodynamics.Math; + +/// +/// Newton-Raphson method for finding the root of a function. +/// +public class NewtonRaphson +{ + /// + /// Solve the equation f(x) = 0 using the Newton-Raphson method. + /// + /// + /// + /// + /// + /// + /// + /// + public static double Solve( + Func function, + Func derivative, + double initialGuess, + double tolerance = 1e-6, + int maxIterations = 100) + { + double x = initialGuess; + + for (int i = 0; i < maxIterations; i++) + { + double fValue = function(x); + double fDerivative = derivative(x); + + if (System.Math.Abs(fDerivative) < 1e-12) // Avoid division by zero or near-zero derivatives + throw new Exception($"Derivative too small (f'(x) = {fDerivative}) at iteration {i}. Adjust initial guess or problem scaling."); + + // Update the root estimate using Newton-Raphson formula + double xNew = x - fValue / fDerivative; + + // Check for convergence (absolute and relative tolerance) + if (System.Math.Abs(xNew - x) < tolerance || System.Math.Abs(xNew - x) < tolerance * System.Math.Abs(xNew)) + return xNew; + + if (double.IsNaN(xNew) || double.IsInfinity(xNew)) + throw new Exception("Newton-Raphson method diverged."); + + x = xNew; + } + + + throw new Exception("Newton-Raphson method did not converge within the maximum number of iterations."); + } + + public static double BoundedNewtonRaphson(Func f, Func df, + double x0, double min, double max, double tolerance, int maxIterations) + { + double x = x0; + for (int i = 0; i < maxIterations; i++) + { + double fx = f(x); + if (System.Math.Abs(fx) < tolerance) + return x; + + double dfx = df(x); + if (System.Math.Abs(dfx) < 1e-10) + break; + + double step = fx / dfx; + double newX = x - step; + + // Bound the solution + if (newX < min) + newX = min; + else if (newX > max) + newX = max; + + if (System.Math.Abs(newX - x) < tolerance) + return newX; + + x = newX; + } + + throw new Exception("Failed to converge within maximum iterations"); + } +} \ No newline at end of file diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Solver.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Solver.cs new file mode 100644 index 00000000..bb4ac035 --- /dev/null +++ b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Solver.cs @@ -0,0 +1,27 @@ +using System; + +namespace IO.Astrodynamics.Math; + +public class Solver +{ + /// + /// Solve the quadratic equation ax^2 + bx + c = 0. + /// + /// + /// + /// + /// + /// + public static double SolveQuadratic(double a, double b, double c) + { + double discriminant = b * b - 4 * a * c; + if (discriminant < 0) + throw new InvalidOperationException("No real roots for the polynomial."); + + double root1 = (-b + System.Math.Sqrt(discriminant)) / (2 * a); + double root2 = (-b - System.Math.Sqrt(discriminant)) / (2 * a); + + // Select the positive root (physically meaningful solution) + return System.Math.Max(root1, root2); + } +} \ No newline at end of file diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Vector3.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Vector3.cs index c72bb595..fa9a12eb 100644 --- a/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Vector3.cs +++ b/IO.Astrodynamics.Net/IO.Astrodynamics/Math/Vector3.cs @@ -20,7 +20,12 @@ public Vector3(double x, double y, double z) public double Magnitude() { - return System.Math.Sqrt(X * X + Y * Y + Z * Z); + return System.Math.Sqrt(MagnitudeSquared()); + } + + public double MagnitudeSquared() + { + return X * X + Y * Y + Z * Z; } public Vector3 Normalize() diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/InitialOrbitDetermination.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/InitialOrbitDetermination.cs new file mode 100644 index 00000000..af3819fd --- /dev/null +++ b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/InitialOrbitDetermination.cs @@ -0,0 +1,223 @@ +using System; +using System.Linq; +using IO.Astrodynamics.Body; +using IO.Astrodynamics.Coordinates; +using IO.Astrodynamics.Frames; +using IO.Astrodynamics.Math; +using IO.Astrodynamics.TimeSystem; + +namespace IO.Astrodynamics.OrbitalParameters; + +public class InitialOrbitDetermination +{ + /// + /// Create orbital parameters from observations + /// + /// + /// + /// + /// + /// + /// + /// + public static OrbitDetermination CreateFromObservation_Gauss(Equatorial observation1, Equatorial observation2, Equatorial observation3, ILocalizable observer, + CelestialItem expectedCenterOfMotion, double expectedRangeFromObserver) + { + var distanceScale = expectedCenterOfMotion.IsSun ? Constants.AU : 1E03; + + double mu = expectedCenterOfMotion.GM / System.Math.Pow(distanceScale, 3); + // Step 1: Compute observer positions with improved scaling + Vector3 R1 = observer + .GetEphemeris(observation1.Epoch, expectedCenterOfMotion, Frame.ICRF, Aberration.LT) + .ToStateVector() + .Position / distanceScale; + + Vector3 R2 = observer + .GetEphemeris(observation2.Epoch, expectedCenterOfMotion, Frame.ICRF, Aberration.LT) + .ToStateVector() + .Position / distanceScale; + + Vector3 R3 = observer + .GetEphemeris(observation3.Epoch, expectedCenterOfMotion, Frame.ICRF, Aberration.LT) + .ToStateVector() + .Position / distanceScale; + + // Step 2: Convert equatorial coordinates to unit direction vectors + Vector3 rhoHat1 = observation1.ToDirection(); + Vector3 rhoHat2 = observation2.ToDirection(); + Vector3 rhoHat3 = observation3.ToDirection(); + + var score = CheckObservationQuality(new[] { R1, R2, R3 }, new[] { rhoHat1, rhoHat2, rhoHat3 }, new[] { observation1.Epoch, observation2.Epoch, observation3.Epoch }); + + // Step 3: Time differences with improved precision + double tau1 = (observation1.Epoch - observation2.Epoch).TotalSeconds; + double tau3 = (observation3.Epoch - observation2.Epoch).TotalSeconds; + double tau = tau3 - tau1; + + // Step 4: Compute determinants with improved numerical stability + Vector3 p1 = rhoHat2.Cross(rhoHat3); + Vector3 p2 = rhoHat1.Cross(rhoHat3); + Vector3 p3 = rhoHat1.Cross(rhoHat2); + + double d0 = rhoHat1 * p1; + double d11 = R1 * p1; + double d12 = R1 * p2; + double d13 = R1 * p3; + double d21 = R2 * p1; + double d22 = R2 * p2; + double d23 = R2 * p3; + double d31 = R3 * p1; + double d32 = R3 * p2; + double d33 = R3 * p3; + + // Step 5: Compute scaled coefficients with improved numerical stability + double A = (1.0 / d0) * (-d12 * (tau3 / tau) + d22 + d32 * (tau1 / tau)); + double B = (1.0 / (6.0 * d0 * tau)) * + (-d12 * tau3 * (tau * tau - tau3 * tau3) + d32 * tau1 * (tau * tau - tau1 * tau1)); + + // Step 6: Solve eighth-degree polynomial with improved coefficients + double E = R2 * rhoHat2; + + double polynomialA = -(A * A + 2.0 * A * E + R2.MagnitudeSquared()); + double polynomialB = -2.0 * mu * B * (A + E); + double polynomialC = -(mu * mu) * (B * B); + + Func function = r2 => System.Math.Pow(r2, 8) + polynomialA * System.Math.Pow(r2, 6) + + polynomialB * System.Math.Pow(r2, 3) + polynomialC; + + Func derivative = r2 => 8.0 * System.Math.Pow(r2, 7) + + 6.0 * polynomialA * System.Math.Pow(r2, 5) + + 3.0 * polynomialB * System.Math.Pow(r2, 2); + + // Use improved initial guess based on expected range + double scaledInitialGuess = expectedRangeFromObserver / distanceScale; + double root_distance = NewtonRaphson.Solve(function, derivative, scaledInitialGuess, 1E-12, 1000); + + double r23 = System.Math.Pow(root_distance, 3); + double numerator = 6 * (d31 * (tau1 / tau3) + d21 * (tau / tau3)) * r23 + + mu * d31 * (tau * tau - tau1 * tau1) * (tau1 / tau3); + + double denominator = 6 * r23 + mu * (tau * tau - tau3 * tau3); + + double rho1 = (1 / d0) * ((numerator / denominator) - d11); + + // Calculate ρ₃ (rho3) + double numerator3 = 6 * (d13 * (tau3 / tau1) - d23 * (tau / tau1)) * r23 + mu * d13 * (tau * tau - tau3 * tau3) * (tau3 / tau1); + + double denominator3 = 6 * r23 + mu * (tau * tau - tau1 * tau1); + + double rho3 = (1 / d0) * ((numerator3 / denominator3) - d33); + + // Calculate ρ₂ (rho2) + double rho2 = A + ((mu * B) / r23); + + // Step 7: Compute position vectors + Vector3 r1 = rhoHat1 * rho1 + R1; + Vector3 r2 = rhoHat2 * rho2 + R2; + Vector3 r3 = rhoHat3 * rho3 + R3; + + // Calculate higher-order terms for f1 and f3 + double r2Cubed = System.Math.Pow(r2.Magnitude(), 3); + double r2Sixth = System.Math.Pow(r2.Magnitude(), 6); + + double f1 = 1 - (mu * System.Math.Pow(tau1, 2)) / (2 * r2Cubed) + + (mu * mu * System.Math.Pow(tau1, 4)) / (24 * r2Sixth); + + double f3 = 1 - (mu * System.Math.Pow(tau3, 2)) / (2 * r2Cubed) + + (mu * mu * System.Math.Pow(tau3, 4)) / (24 * r2Sixth); + +// Calculate higher-order terms for g1 and g3 + double g1 = tau1 - (mu * System.Math.Pow(tau1, 3)) / (6 * r2Cubed) + + (mu * mu * System.Math.Pow(tau1, 5)) / (120 * r2Sixth); + + double g3 = tau3 - (mu * System.Math.Pow(tau3, 3)) / (6 * r2Cubed) + + (mu * mu * System.Math.Pow(tau3, 5)) / (120 * r2Sixth); + + + // Step 9: Compute velocity vector using full Lagrange formulation + Vector3 v2 = (r1 * -f3 + r3 * f1) * (1 / (f1 * g3 - f3 * g1)); + + // Step 10: Convert position and velocity to Keplerian elements + var sv = new StateVector(r2 * distanceScale, v2 * distanceScale, expectedCenterOfMotion, observation2.Epoch, Frame.ICRF); + return new OrbitDetermination(sv, score.GlobalReliability, score.GeometricReliability, score.TemporalSpacingReliability, score.CenterOfMotionReliability); + } + + private static QualityMetrics CheckObservationQuality(Vector3[] observerPositions, + Vector3[] apparentDirections, + Time[] observationTimes) + { + if (observerPositions.Length != 3 || apparentDirections.Length != 3 || observationTimes.Length != 3) + throw new ArgumentException("Exactly 3 observations are required"); + + var sightLines = apparentDirections.Select(dir => dir.Normalize()).ToList(); + + // geometrics quality + var normal = sightLines[0].Cross(sightLines[1]).Normalize(); + var coplanarity = 1 - System.Math.Abs(normal * sightLines[2]); + + var angle1 = System.Math.Acos(sightLines[0] * sightLines[1]); + var angle2 = System.Math.Acos(sightLines[1] * sightLines[2]); + + var normalizedAngle1 = angle1 / (System.Math.PI / 2); + var normalizedAngle2 = angle2 / (System.Math.PI / 2); + + const double geometricOptimalAngle = 0.5; // 45° normalisé + var angleQuality1 = 1 - System.Math.Abs(normalizedAngle1 - geometricOptimalAngle); + var angleQuality2 = 1 - System.Math.Abs(normalizedAngle2 - geometricOptimalAngle); + + var geometricQuality = coplanarity * System.Math.Min(angleQuality1, angleQuality2); + + // temporal quality + TimeSpan totalDuration = observationTimes[2] - observationTimes[0]; + TimeSpan idealSpacing = totalDuration / 2; + TimeSpan middleSpacing = observationTimes[1] - observationTimes[0]; + double temporalQuality = 1 - System.Math.Abs(middleSpacing.TotalSeconds - idealSpacing.TotalSeconds) / totalDuration.TotalSeconds; + + // center of motion quality + var centerOfMotionQuality = 0.0; + for (int i = 0; i < 3; i++) + { + var observerDirection = observerPositions[i].Normalize(); + var angle = System.Math.Abs(sightLines[i] * observerDirection); + const double optimalAngle = 0.25; + var normalizedDistance = System.Math.Abs(angle - optimalAngle); + var quality = System.Math.Exp(-2 * normalizedDistance); + + centerOfMotionQuality += quality; + } + + centerOfMotionQuality /= 3; + + double W_GEOMETRIC = 0.15; + double W_TEMPORAL = 0.05; + double W_CENTER = 0.8; + + if (observerPositions[0].Magnitude() < 1E05) + { + W_GEOMETRIC = 0.4; + W_TEMPORAL = 0.5; + W_CENTER = 0.1; + } + + var globalQuality = + W_GEOMETRIC * geometricQuality + + W_TEMPORAL * temporalQuality + + W_CENTER * centerOfMotionQuality; + + return new QualityMetrics + { + GeometricReliability = geometricQuality, + TemporalSpacingReliability = temporalQuality, + CenterOfMotionReliability = centerOfMotionQuality, + GlobalReliability = globalQuality + }; + } + + public record QualityMetrics + { + public double GeometricReliability { get; init; } + public double TemporalSpacingReliability { get; init; } + public double CenterOfMotionReliability { get; init; } + public double GlobalReliability { get; init; } + } +} \ No newline at end of file diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/KeplerianElements.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/KeplerianElements.cs index 72b6b817..394ef469 100644 --- a/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/KeplerianElements.cs +++ b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/KeplerianElements.cs @@ -200,7 +200,7 @@ public override int GetHashCode() public override string ToString() { - return $"Epoch : {Epoch.ToString()} A : {A} Ecc. : {E} Inc. : {I} AN : {RAAN} AOP : {AOP} M : {M} Frame : {Frame.Name}"; + return $"Epoch : {Epoch.ToString()} A : {A}, Ecc. : {E}, Inc. : {I}, AN : {RAAN}, AOP : {AOP}, M : {M}, Frame : {Frame.Name}"; } #endregion } diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitDetermination.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitDetermination.cs new file mode 100644 index 00000000..68c49cf7 --- /dev/null +++ b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitDetermination.cs @@ -0,0 +1,54 @@ +using System; + +namespace IO.Astrodynamics.OrbitalParameters; + +public record OrbitDetermination +{ + public OrbitalParameters OrbitalParameters { get; } + public double Reliability { get; } + public double GeometricReliability { get; } + public double TemporalReliability { get; } + public double CenterOfMotionQualityReliability { get; } + + public OrbitDetermination(OrbitalParameters orbitalParameters, double reliability, double geometricReliability, double temporalReliability, double centerOfMotionReliability) + { + if (orbitalParameters == null) throw new ArgumentNullException(nameof(orbitalParameters)); + ArgumentOutOfRangeException.ThrowIfNegative(reliability); + if (reliability > 1) + { + throw new ArgumentOutOfRangeException(nameof(reliability), "Reliability must be between 0 and 1."); + } + + ArgumentOutOfRangeException.ThrowIfNegative(centerOfMotionReliability); + if (centerOfMotionReliability > 1) + { + throw new ArgumentOutOfRangeException(nameof(centerOfMotionReliability), "Center of motion quality Reliability must be between 0 and 1."); + } + + ArgumentOutOfRangeException.ThrowIfNegative(temporalReliability); + if (temporalReliability > 1) + { + throw new ArgumentOutOfRangeException(nameof(temporalReliability), "Temporal Reliability must be between 0 and 1."); + } + + ArgumentOutOfRangeException.ThrowIfNegative(geometricReliability); + if (geometricReliability > 1) + { + throw new ArgumentOutOfRangeException(nameof(geometricReliability), "Geometric Reliability must be between 0 and 1."); + } + + OrbitalParameters = orbitalParameters; + Reliability = reliability; + GeometricReliability = geometricReliability; + TemporalReliability = temporalReliability; + CenterOfMotionQualityReliability = centerOfMotionReliability; + } + + public override string ToString() + { + return $"Reliability : {Reliability * 100}%\n" + + $"Geometric reliability : {GeometricReliability * 100}%\n" + + $"Temporal reliability : {TemporalReliability * 100}%\n" + + $"Center of motion reliability : {CenterOfMotionQualityReliability * 100}%"; + } +} \ No newline at end of file diff --git a/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitalParameters.cs b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitalParameters.cs index 9ced1f74..f71476d0 100644 --- a/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitalParameters.cs +++ b/IO.Astrodynamics.Net/IO.Astrodynamics/OrbitalParameters/OrbitalParameters.cs @@ -4,6 +4,7 @@ using IO.Astrodynamics.Coordinates; using IO.Astrodynamics.Frames; using IO.Astrodynamics.Math; +using IO.Astrodynamics.SolarSystemObjects; using IO.Astrodynamics.TimeSystem; namespace IO.Astrodynamics.OrbitalParameters; @@ -934,6 +935,10 @@ public double SemiLatusRectum() return hNorm * hNorm / Observer.GM; } + + + #region Operators + public override bool Equals(object obj) { return Equals(obj as OrbitalParameters); @@ -959,4 +964,6 @@ public override int GetHashCode() { return !(left == right); } + + #endregion } \ No newline at end of file