• Welcome to the new COTI server. We've moved the Citizens to a new server. Please let us know in the COTI Website issue forum if you find any problems.

Rendezvous Math

whartung

SOC-14 5K
So, year or so ago, I did this post:


Someone was kind enough to upvote it the other day, and I thought I could readdress it.

With the new ChatGPT features to learn math to blockheads like me, we were able to solve the equations and get some numbers. One of the results were interesting and unintuitive, but it works out.

To summarize, the scenario is you have a ship trying to rendezvous with a planet. The initial use case is the ship is trailing the planet. There's no gravity involved, and the planet is going in a straight line (these are magic planets that fit right in with Traveller M-Drives).

When a ship is trailing the planet, an the ship is not going as fast as the planet, then in order to catch the planet it must first accelerate to be faster than the planet, and the decelerate until it matches the planet velocity and its position

So, this was the core math involved:

sx + sv + .5 * a * t1^2 + (sv + a * t1) + .5 * -a * t2^2 = px + pv * (t1 + t2)
sv + (a * t1) - (a * t2) = pv

What this says is that the final ship position must be how far it travels under acceleration for period t1 + how far it travels under deceleration (the -a) for period t2, and it must match the planets position which is px (the original planet position) plus how far it travels over the total time (t1 + t2) and speed pv.

At the same time, the ships final velocity, which is it current velocity plus how fast its going after t1 and how much its slowed down at t2. That must match the planets constant velocity.

So, turns out this is a simple algebra problem. Since I have always sucked at algebra, I don't see algebraic solutions.

But ChatGPT does!

The way to solve these two equations was that it took the velocity equation, and rewrote it to calculate t2 in terms of t1.

Like this: t2 = sv + a * t1 - pv / a.

Then you substitutes all instances of t2 in the first equation with this new equation (which I won't do). Then you rewrite that equation in terms of t1.

In the end you get an equation that can be solved as a quadratic equation. Once you have t1 calculated, you can then put that in the formula for t2 and voila!

So, given all that, I have a java program that will calculate this stuff for us.

Turns out there's two fundamental scenarios. Ship trailing the planet, where you need to accelerate and then decelerate. There's then the other side where you need to decelerate and then accelerate. For example, if you're in front of the planet. Using "accelerate" and "decelerate" are a bit confusing because of the signs of everything. For example, to accelerate, you use positive a (say, 10), and decelerate you use negative a (like -10).

But that gets weird. For example, say you're in front of the planet by a large margin. In that case, you'll want to accelerate TOWARD the planet and then decelerate into the planet. But, since you're in front of the planet, you need a "negative" velocity, even though its going "up" (i.e. more negative). Because I visualize acceleration as thrust in the direction I want to go, and deceleration as thrust against the direction I'm going. In the end, it's a rocket with a flame racing out the back, the universe doesn't really care.

That said, while playing with it there was an unintuitive solution. You're in front of the planet, and it turns out the best path is to accelerate AWAY from the planet and then decel into it. But that's the best path.

So, anyway, this code "does the right thing". Give the parameters (sx, sv, px, pv, a), it will tell you what the two phases are, and whether youre accelerating (positive a) or decelerating.

Finally, I did not write this code, the AI did. I just chatted with it for about 6 hours to get it to this state lol.

Given this scenario. You're leaving an Earth size planet (12Kkm in diameter) with a 1G drive. So you accelerate to 100D (1.2Bm).

At the end, you end up having a velocity of 154919.33 m/s.

So, you're arriving at another Earth planet. Should you arrive behind the planet, or in front of the planet? Earth travels at roughly 30km/s.

Here's some example results.

In the first case, you arrive behind the planet at 100D (12B m), your velocity is toward the planet.

The second case, you arrive at the 100D mark in front of the planet, with your velocity toward the planet (since you're in front, your velocity is negative).

Which route gets you into the Star Bar faster?

Code:
--- Calculating Rendezvous ---
Ship Position: 0.00, Ship Velocity: 154919.33
Planet Position: 1200000000.00, Planet Velocity: 30000.00
Acceleration: 10.00
Optimal Strategy: Decelerate first (Trailing scenario).
t1 (Acceleration Time): 1580.17 seconds
t2 (Deceleration Time): 14072.11 seconds
Total Time: 15652.28 seconds

Acceleration: 10.00 m/s²

After t1: Ship Position: 257284244.40 | Planet Position: 1247405219.59
After t2: Ship Position: 1669568429.19 | Planet Position: 1669568429.19

--- Calculating Rendezvous ---
Ship Position: 1200000000.00, Ship Velocity: -154919.33
Planet Position: 0.00, Planet Velocity: 30000.00
Acceleration: 10.00
Optimal Strategy: Decelerate first (Trailing scenario).
t1 (Acceleration Time): 25631.67 seconds
t2 (Deceleration Time): 7139.73 seconds
Total Time: 32771.40 seconds

Acceleration: 10.00 m/s²

After t1: Ship Position: 514071026.84 | Planet Position: 768949992.03
After t2: Ship Position: 983141994.06 | Planet Position: 983141994.06

As you can see, arriving behind the planet gets you there twice as fast. 4.3 hours vs 9.1. Also note for the second scenario, you need to start slowing down right away, and you pass the planet, and then you need to pass the planet, but you're still going faster than the planet, which is why you're decelerating on the final phase.

Anyway, it was a fun project arguing with the AI to get this to work. Answers some interesting questions (to me at least).

For some reason it won't let me attach the code as a file.

Code is in the next message cuz limits.
 
Code:
public class RendezvousCalculator {

    public static void main(String[] args) {
        // Test case: Ship trailing, but traveling too slow
        calculateRendezvous(0, 154919.33, 1200000000, 30000, 10);
        calculateRendezvous(1200000000, -154919.33, 0, 30000, 10);
        calculateRendezvous(0, 0, 0, 30000, 10);
        calculateRendezvous(45000000, 0, 0, 30000, 10);
        calculateRendezvous(0, 10000, 1000000, 30000, 10);
        calculateRendezvous(0, -10000, 1000000, 30000, 10);
        calculateRendezvous(1000000, -10000, 0, 30000, 10);
        // Test case: Ship trailing but traveling too fast
        calculateRendezvous(0, 50000, 1000000, 30000, 10);
        calculateRendezvous(200000000, 10000, 0, 30000, 10);
    }

    public static void calculateRendezvous(double sx, double sv, double px, double pv, double a) {
        System.out.println("\n--- Calculating Rendezvous ---");
        System.out.printf("Ship Position: %.2f, Ship Velocity: %.2f\n", sx, sv);
        System.out.printf("Planet Position: %.2f, Planet Velocity: %.2f\n", px, pv);
        System.out.printf("Acceleration: %.2f\n", a);

        // Calculate for Strategy 1: Trailing scenario (decelerate first)
        double[] resultTrailing = calculateTimes(sx, sv, px, pv, a);
        double t1Trailing = resultTrailing[0];
        double t2Trailing = resultTrailing[1];
        double totalTimeTrailing = t1Trailing + t2Trailing;

        // Check if trailing scenario is valid
        boolean trailingValid = t1Trailing >= 0 && t2Trailing >= 0;

        // If trailing scenario is invalid, switch to leading scenario
        double[] resultLeading = calculateTimes(sx, sv, px, pv, -a);
        double t1Leading = resultLeading[0];
        double t2Leading = resultLeading[1];
        double totalTimeLeading = t1Leading + t2Leading;

        // Check if leading scenario is valid
        boolean leadingValid = t1Leading >= 0 && t2Leading >= 0;

        // Choose the optimal strategy
        if (trailingValid && (!leadingValid || totalTimeTrailing <= totalTimeLeading)) {
            System.out.println("Optimal Strategy: Decelerate first (Trailing scenario).");
            displayResults(sx, sv, px, pv, a, t1Trailing, t2Trailing);
        } else if (leadingValid) {
            System.out.println("Optimal Strategy: Switch to leading scenario.");
            displayResults(sx, sv, px, pv, -a, t1Leading, t2Leading);
        } else {
            System.out.println("No valid solution: Both strategies result in negative values.");
        }
    }

    public static double[] calculateTimes(double sx, double sv, double px, double pv, double a) {
        // Relative position and velocity
        double relativePosition = px - sx;
        double relativeVelocity = pv - sv;

        // Calculate the discriminant for the kinematic equation
        double discriminant = a * relativePosition + 0.5 * (pv * pv - 2 * pv * sv + sv * sv);

        if (discriminant < 0) {
            return new double[]{-1, -1}; // No real solution exists
        }

        // Calculate potential t1 values (acceleration time)
        double sqrtDiscriminant = Math.sqrt(discriminant);
        double t1_1 = (relativeVelocity + sqrtDiscriminant) / a;
        double t1_2 = (relativeVelocity - sqrtDiscriminant) / a;

        // Calculate corresponding t2 values
        double t2_1 = (sv + a * t1_1 - pv) / a;
        double t2_2 = (sv + a * t1_2 - pv) / a;

        // Select the valid (t1, t2) pair where both t1 and t2 are non-negative
        if (t1_1 >= 0 && t2_1 >= 0) {
            return new double[]{t1_1, t2_1};
        } else if (t1_2 >= 0 && t2_2 >= 0) {
            return new double[]{t1_2, t2_2};
        } else {
            return new double[]{-1, -1}; // No valid solution
        }
    }

    public static void displayResults(double sx, double sv, double px, double pv, double a, double t1, double t2) {
        String phase1Label = (a > 0) ? "Acceleration" : "Deceleration";
        String phase2Label = (a > 0) ? "Deceleration" : "Acceleration";

        System.out.printf("t1 (%s Time): %.2f seconds\n", phase1Label, t1);
        System.out.printf("t2 (%s Time): %.2f seconds\n", phase2Label, t2);
        System.out.printf("Total Time: %.2f seconds\n", t1 + t2);
        System.out.printf("Acceleration: %.2f m/s²\n", a);

        // Position after t1
        double shipPositionAfterT1 = sx + sv * t1 + 0.5 * a * t1 * t1;
        double planetPositionAfterT1 = px + pv * t1;
        System.out.printf("\nAfter t1: Ship Position: %.2f | Planet Position: %.2f\n", shipPositionAfterT1, planetPositionAfterT1);

        // Position after t2
        double svAfterT1 = sv + a * t1;
        double shipPositionAfterT2 = shipPositionAfterT1 + svAfterT1 * t2 - 0.5 * a * t2 * t2;
        double planetPositionAfterT2 = planetPositionAfterT1 + pv * t2;
        System.out.printf("After t2: Ship Position: %.2f | Planet Position: %.2f\n", shipPositionAfterT2, planetPositionAfterT2);
    }
}
 
Back
Top