From ed96b5d27b4f7112452a49749fb5584e96e3e85c Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Sun, 22 Oct 2023 14:00:19 -0700 Subject: [PATCH] Rename to Two-Impulse Maneuver and support fixed time Signed-off-by: Lamont Granquist --- Localization/en-us.cfg | 4 +- MechJeb2/Maneuver/OperationTransfer.cs | 41 ++++--- MechJeb2/MechJeb2.csproj | 2 +- ...lanarTransfer.cs => TwoImpulseTransfer.cs} | 112 ++++++++++-------- MechJeb2/OrbitalManeuverCalculator.cs | 4 +- ...ferTests.cs => TwoImpulseTransferTests.cs} | 14 +-- MechJebLibTest/MechJebLibTest.csproj | 2 +- 7 files changed, 101 insertions(+), 78 deletions(-) rename MechJeb2/MechJebLib/Maneuvers/{CoplanarTransfer.cs => TwoImpulseTransfer.cs} (64%) rename MechJebLibTest/ManeuversTests/{CoplanarTransferTests.cs => TwoImpulseTransferTests.cs} (87%) diff --git a/Localization/en-us.cfg b/Localization/en-us.cfg index 7b3b1697..76f631d4 100644 --- a/Localization/en-us.cfg +++ b/Localization/en-us.cfg @@ -114,10 +114,10 @@ Localization #MechJeb_adv_Exception4 = Invalid point selected. //Hohmann transfer - #MechJeb_Hohm_title = bi-impulsive (Hohmann) transfer to target + #MechJeb_Hohm_title = two impulse (Hohmann) transfer to target #MechJeb_Hohm_intercept_only = no insertion burn (impact/flyby) #MechJeb_Hohm_simpleTransfer = coplanar maneuver - #MechJeb_Hohm_Label1 = lag time offset + #MechJeb_Hohm_Label1 = rendezvous time offset #MechJeb_Hohm_Exception1 = must select a target for the maneuver. #MechJeb_Hohm_Exception2 = target for maneuver must be in the same sphere of influence. diff --git a/MechJeb2/Maneuver/OperationTransfer.cs b/MechJeb2/Maneuver/OperationTransfer.cs index 9a366f67..7b8eed99 100644 --- a/MechJeb2/Maneuver/OperationTransfer.cs +++ b/MechJeb2/Maneuver/OperationTransfer.cs @@ -61,7 +61,6 @@ public override void DoParametersGUI(Orbit o, double universalTime, MechJebModul GUILayout.EndHorizontal(); if (Rendezvous) GuiUtils.SimpleTextBox(Localizer.Format("#MechJeb_Hohm_Label1"), LagTime, "sec"); //fractional target period offset - _timeSelector.DoChooseTimeGUI(); } @@ -75,22 +74,6 @@ protected override List MakeNodesImpl(Orbit o, double univer new OperationException( Localizer.Format("#MechJeb_Hohm_Exception2")); //target for bi-impulsive transfer must be in the same sphere of influence. - bool anExists = o.AscendingNodeExists(target.TargetOrbit); - bool dnExists = o.DescendingNodeExists(target.TargetOrbit); - - switch (_timeSelector.TimeReference) - { - case TimeReference.REL_ASCENDING when !anExists: - throw new OperationException(Localizer.Format("#MechJeb_Hohm_Exception3")); //ascending node with target doesn't exist. - case TimeReference.REL_DESCENDING when !dnExists: - throw new OperationException(Localizer.Format("#MechJeb_Hohm_Exception4")); //descending node with target doesn't exist. - case TimeReference.REL_NEAREST_AD when !(anExists || dnExists): - throw new OperationException( - Localizer.Format("#MechJeb_Hohm_Exception5")); //neither ascending nor descending node with target exists. - } - - double ut = _timeSelector.ComputeManeuverTime(o, universalTime, target); - if (target.Target is CelestialBody && Capture && PlanCapture) ErrorMessage = "Insertion burn to a celestial with an SOI is not supported by this maneuver. A Transfer-to-Moon maneuver needs to be written to properly support this case."; @@ -99,8 +82,30 @@ protected override List MakeNodesImpl(Orbit o, double univer double lagTime = Rendezvous ? LagTime.val : 0; + bool fixedTime = false; + + if (_timeSelector.TimeReference != TimeReference.COMPUTED) + { + bool anExists = o.AscendingNodeExists(target.TargetOrbit); + bool dnExists = o.DescendingNodeExists(target.TargetOrbit); + + if (_timeSelector.TimeReference == TimeReference.REL_ASCENDING && !anExists) + throw new OperationException(Localizer.Format("#MechJeb_Hohm_Exception3")); //ascending node with target doesn't exist. + + if (_timeSelector.TimeReference == TimeReference.REL_DESCENDING && !dnExists) + throw new OperationException(Localizer.Format("#MechJeb_Hohm_Exception4")); //descending node with target doesn't exist. + + if (_timeSelector.TimeReference == TimeReference.REL_NEAREST_AD && !(anExists || dnExists)) + throw new OperationException( + Localizer.Format("#MechJeb_Hohm_Exception5")); //neither ascending nor descending node with target exists. + + universalTime = _timeSelector.ComputeManeuverTime(o, universalTime, target); + fixedTime = true; + } + (Vector3d dV1, double ut1, Vector3d dV2, double ut2) = - OrbitalManeuverCalculator.DeltaVAndTimeForHohmannTransfer(o, targetOrbit, ut, lagTime, Coplanar, Rendezvous, Capture); + OrbitalManeuverCalculator.DeltaVAndTimeForHohmannTransfer(o, targetOrbit, universalTime, lagTime, fixedTime, Coplanar, Rendezvous, + Capture); if (Capture && PlanCapture) return new List { new ManeuverParameters(dV1, ut1), new ManeuverParameters(dV2, ut2) }; diff --git a/MechJeb2/MechJeb2.csproj b/MechJeb2/MechJeb2.csproj index eef553e4..36e288ca 100644 --- a/MechJeb2/MechJeb2.csproj +++ b/MechJeb2/MechJeb2.csproj @@ -114,7 +114,7 @@ - + diff --git a/MechJeb2/MechJebLib/Maneuvers/CoplanarTransfer.cs b/MechJeb2/MechJebLib/Maneuvers/TwoImpulseTransfer.cs similarity index 64% rename from MechJeb2/MechJebLib/Maneuvers/CoplanarTransfer.cs rename to MechJeb2/MechJebLib/Maneuvers/TwoImpulseTransfer.cs index 975a5210..5411e74f 100644 --- a/MechJeb2/MechJebLib/Maneuvers/CoplanarTransfer.cs +++ b/MechJeb2/MechJebLib/Maneuvers/TwoImpulseTransfer.cs @@ -7,7 +7,7 @@ namespace MechJebLib.Maneuvers { - public class CoplanarTransfer + public static class TwoImpulseTransfer { private struct Args { @@ -54,8 +54,8 @@ private static void NLPFunction(double[] x, ref double func, object obj) func = dv1.magnitude; } - public static (V3 dv1, double dt1, V3 dv2, double dt2) Maneuver(double mu, V3 r1, V3 v1, V3 r2, V3 v2, double dtguess, double offsetGuess, - bool coplanar = true, bool rendezvous = true, bool capture = true, double dtmin = double.NegativeInfinity, + private static (V3 dv1, double dt1, V3 dv2, double dt2) Maneuver(double mu, V3 r1, V3 v1, V3 r2, V3 v2, double dtguess, double offsetGuess, + bool coplanar = true, bool capture = true, double dtmin = double.NegativeInfinity, double dtmax = double.PositiveInfinity, double ttmin = double.NegativeInfinity, double ttmax = double.PositiveInfinity, double offsetMin = double.NegativeInfinity, double offsetMax = double.PositiveInfinity, bool optguard = false) @@ -138,7 +138,7 @@ private static void NLPFunction(double[] x, ref double func, object obj) if (rep.terminationtype < 0) throw new Exception( - $"CoplanarTransfer.Maneuver({mu}, {r1}, {v1}, {r2}, {v2}): CG solver terminated abnormally: {rep.terminationtype}" + $"TwoImpulseTransfer.Maneuver({mu}, {r1}, {v1}, {r2}, {v2}): CG solver terminated abnormally: {rep.terminationtype}" ); if (optguard) @@ -157,64 +157,82 @@ private static void NLPFunction(double[] x, ref double func, object obj) return (dv1 * scale.VelocityScale, x[0] * scale.TimeScale, dv2 * scale.VelocityScale, (x[0] + x[1]) * scale.TimeScale); } - public static (V3 dv1, double dt1, V3 dv2, double dt2) NextManeuver(double mu, V3 r1, V3 v1, V3 r2, V3 v2, int maxiter = 50, - double lagTime = double.NaN, bool coplanar = true, bool rendezvous = true, bool capture = true, bool optguard = false) + private static (V3 dv1, double dt1, V3 dv2, double dt2) ManeuverInternal(double mu, V3 r1, V3 v1, V3 r2, V3 v2, double dtguess, + double lagTime = double.NaN, bool coplanar = true, bool rendezvous = true, bool capture = true, + bool fixedtime = false, bool optguard = false) { - double synodicPeriod = Maths.SynodicPeriod(mu, r1, v1, r2, v2); - double targetPeriod = Maths.PeriodFromStateVectors(mu, r2, v2); - double dtguess = 0; + V3 dv1, dv2; + double dt1, dt2; - for (int iter = 0; iter < maxiter; iter++) + double dtmin = double.NegativeInfinity; + double dtmax = double.PositiveInfinity; + + if (fixedtime) { - V3 dv1, dv2; - double dt1, dt2; - double offsetGuess = 0; + dtmin = dtguess; + dtmax = dtguess; + } - if (rendezvous) + if (rendezvous) + { + double offsetGuess = 0; + double offsetMin = 0; + double offsetMax = 0; + if (lagTime.IsFinite()) { - double offsetMin = 0; - double offsetMax = 0; - if (lagTime.IsFinite()) - { - offsetMin = -lagTime; - offsetMax = -lagTime; - offsetGuess = -lagTime; - } - - (dv1, dt1, dv2, dt2) = - Maneuver(mu, r1, v1, r2, v2, dtguess, offsetGuess, offsetMin: offsetMin, offsetMax: offsetMax, coplanar: coplanar, - rendezvous: true, capture: capture, - optguard: optguard); + offsetMin = -lagTime; + offsetMax = -lagTime; + offsetGuess = -lagTime; } - else + (dv1, dt1, dv2, dt2) = + Maneuver(mu, r1, v1, r2, v2, dtguess, offsetGuess, dtmin: dtmin, dtmax: dtmax, offsetMin: offsetMin, offsetMax: offsetMax, coplanar: coplanar, capture: capture, optguard: optguard); + } + else + { + (dv1, dt1, dv2, dt2) = + Maneuver(mu, r1, v1, r2, v2, dtguess, 0, dtmin: dtmin, dtmax: dtmax , coplanar: coplanar, capture: capture, optguard: optguard); + + // we have to try the other side of the target orbit since we might get eg. the DN instead of the AN when the AN is closer + // (this may be insufficient and may need more of a search box but then we're O(N^2) and i think basinhopping or porkchop + // plots will be the better solution) + double targetPeriod = Maths.PeriodFromStateVectors(mu, r2, v2); + + (V3 a, double b, V3 c, double d) = + Maneuver(mu, r1, v1, r2, v2, dtguess, targetPeriod * 0.5, coplanar: coplanar, capture: capture, optguard: optguard); + + if (b > 0 && (b < dt1 || dt1 < 0)) { - (dv1, dt1, dv2, dt2) = - Maneuver(mu, r1, v1, r2, v2, dtguess, offsetGuess, coplanar, false, capture, - optguard: optguard); - - // we have to try the other side of the target orbit since we might get eg. the DN instead of the AN when the AN is closer - // (this may be insufficient and may need more of a search box but then we're O(N^2) and i think basinhopping or porkchop - // plots will be the better solution) - - (V3 a, double b, V3 c, double d) = - Maneuver(mu, r1, v1, r2, v2, dtguess, targetPeriod * 0.5, coplanar, optguard: optguard, - rendezvous: false, capture: capture); - if (b > 0 && (b < dt1 || dt1 < 0)) - { - dv1 = a; - dt1 = b; - dv2 = c; - dt2 = d; - } + dv1 = a; + dt1 = b; + dv2 = c; + dt2 = d; } + } + + return (dv1, dt1, dv2, dt2); + } + + public static (V3 dv1, double dt1, V3 dv2, double dt2) NextManeuver(double mu, V3 r1, V3 v1, V3 r2, V3 v2, int maxiter = 50, + double lagTime = double.NaN, bool coplanar = true, bool rendezvous = true, bool capture = true, + bool fixedTime = false, bool optguard = false) + { + double synodicPeriod = Maths.SynodicPeriod(mu, r1, v1, r2, v2); + + if (fixedTime) + return ManeuverInternal(mu, r1, v1, r2, v2, dtguess: 0, coplanar: coplanar, rendezvous: rendezvous, capture: capture, optguard: optguard, lagTime:lagTime, fixedtime: true); + + double dtguess = 0; + for (int iter = 0; iter < maxiter; iter++) + { + (V3 dv1, double dt1, V3 dv2, double dt2) = ManeuverInternal(mu, r1, v1, r2, v2, dtguess: dtguess, coplanar: coplanar, rendezvous: rendezvous, capture: capture, optguard: optguard, lagTime:lagTime); if (dt1 > 0) return (dv1, dt1, dv2, dt2); dtguess += synodicPeriod * 0.10; } - throw new MechJebLibException($"CoplanarTransfer.NextManeuver({mu}, {r1}, {v1}, {v2}, {r2}): too many iterations"); + throw new MechJebLibException($"TwoImpulseTransfer.NextManeuver({mu}, {r1}, {v1}, {v2}, {r2}): too many iterations"); } } } diff --git a/MechJeb2/OrbitalManeuverCalculator.cs b/MechJeb2/OrbitalManeuverCalculator.cs index d13b29fc..81fd8f80 100644 --- a/MechJeb2/OrbitalManeuverCalculator.cs +++ b/MechJeb2/OrbitalManeuverCalculator.cs @@ -154,14 +154,14 @@ public static Vector3d DeltaVAndTimeToMatchPlanesDescending(Orbit o, Orbit targe //Assumes o and target are in approximately the same plane, and orbiting in the same direction. //Also assumes that o is a perfectly circular orbit (though result should be OK for small eccentricity). public static ( Vector3d dV1, double UT1, Vector3d dV2, double UT2) DeltaVAndTimeForHohmannTransfer(Orbit o, Orbit target, double ut, - double lagTime = double.NaN, + double lagTime = double.NaN, bool fixedTime = false, bool coplanar = true, bool rendezvous = true, bool capture = true) { (V3 r1, V3 v1) = o.RightHandedStateVectorsAtUT(ut); (V3 r2, V3 v2) = target.RightHandedStateVectorsAtUT(ut); (V3 dv1, double dt1, V3 dv2, double dt2) = - CoplanarTransfer.NextManeuver(o.referenceBody.gravParameter, r1, v1, r2, v2, lagTime: lagTime, coplanar: coplanar, + TwoImpulseTransfer.NextManeuver(o.referenceBody.gravParameter, r1, v1, r2, v2, lagTime: lagTime, coplanar: coplanar, rendezvous: rendezvous, capture: capture); return (dv1.V3ToWorld(), ut + dt1, dv2.V3ToWorld(), ut + dt2); diff --git a/MechJebLibTest/ManeuversTests/CoplanarTransferTests.cs b/MechJebLibTest/ManeuversTests/TwoImpulseTransferTests.cs similarity index 87% rename from MechJebLibTest/ManeuversTests/CoplanarTransferTests.cs rename to MechJebLibTest/ManeuversTests/TwoImpulseTransferTests.cs index 0ea6445d..5da28017 100644 --- a/MechJebLibTest/ManeuversTests/CoplanarTransferTests.cs +++ b/MechJebLibTest/ManeuversTests/TwoImpulseTransferTests.cs @@ -11,11 +11,11 @@ namespace MechJebLibTest.ManeuversTests { - public class CoplanarTransferTests + public class TwoImpulseTransferTests { private readonly ITestOutputHelper _testOutputHelper; - public CoplanarTransferTests(ITestOutputHelper testOutputHelper) + public TwoImpulseTransferTests(ITestOutputHelper testOutputHelper) { _testOutputHelper = testOutputHelper; } @@ -50,16 +50,16 @@ private void HohmannTest() if (synodicPeriod > 1000) continue; - (V3 dv1, double dt1, V3 dv2, double dt2) = CoplanarTransfer.NextManeuver(mu, r1, v1, r2, v2); + (V3 dv1, double dt1, V3 dv2, double dt2) = TwoImpulseTransfer.NextManeuver(mu, r1, v1, r2, v2); (double dv1Hoh, double dv2Hoh, double ttHoh, double _) = Maths.HohmannTransferParameters(mu, r1, r2); dv1.magnitude.ShouldEqual(Abs(dv1Hoh), 1e-6); dv2.magnitude.ShouldEqual(Abs(dv2Hoh), 1e-6); - (dt2-dt1).ShouldEqual(ttHoh, 1e-3); + (dt2 - dt1).ShouldEqual(ttHoh, 1e-3); (V3 rburn1, V3 vburn1) = Shepperd.Solve(mu, dt1, r1, v1); - (V3 rburn2, V3 vburn2) = Shepperd.Solve(mu, dt2-dt1, rburn1, vburn1 + dv1); + (V3 rburn2, V3 vburn2) = Shepperd.Solve(mu, dt2 - dt1, rburn1, vburn1 + dv1); (V3 rf, V3 vf) = Shepperd.Solve(mu, dt2, r2, v2); rf.ShouldEqual(rburn2, 1e-6); @@ -77,7 +77,7 @@ private void GeoTestFixed() double nu = 3.24639265358979; (V3 r2, V3 v2) = Maths.StateVectorsFromKeplerian(mu, 42164000, 0, 0, 0, 0, nu); - (V3 dv1, double dt1, V3 dv2, double dt2) = CoplanarTransfer.NextManeuver(mu, r1, v1, r2, v2, coplanar: false); + (V3 dv1, double dt1, V3 dv2, double dt2) = TwoImpulseTransfer.NextManeuver(mu, r1, v1, r2, v2, coplanar: false); double dv = dv1.magnitude + dv2.magnitude; (V3 rburn1, V3 vburn1) = Shepperd.Solve(mu, dt1, r1, v1); (V3 rburn2, V3 vburn2) = Shepperd.Solve(mu, dt2, rburn1, vburn1 + dv1); @@ -100,7 +100,7 @@ private void GeoTestFree() (V3 r2, V3 v2) = Maths.StateVectorsFromKeplerian(mu, 42164000, 0, 0, 0, 0, 0); - (V3 dv1, double dt1, V3 dv2, double dt2) = CoplanarTransfer.NextManeuver(mu, r1, v1, r2, v2, coplanar: false, rendezvous: false); + (V3 dv1, double dt1, V3 dv2, double dt2) = TwoImpulseTransfer.NextManeuver(mu, r1, v1, r2, v2, coplanar: false, rendezvous: false); double dv = dv1.magnitude + dv2.magnitude; (V3 rburn1, V3 vburn1) = Shepperd.Solve(mu, dt1, r1, v1); (V3 rburn2, V3 vburn2) = Shepperd.Solve(mu, dt2, rburn1, vburn1 + dv1); diff --git a/MechJebLibTest/MechJebLibTest.csproj b/MechJebLibTest/MechJebLibTest.csproj index aaef08f7..348ef40d 100644 --- a/MechJebLibTest/MechJebLibTest.csproj +++ b/MechJebLibTest/MechJebLibTest.csproj @@ -59,7 +59,7 @@ - +