TwoImpulseTransfer.java

/* Copyright 2002-2026 CS GROUP
 * Licensed to CS GROUP (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.orekit.propagation.relative;

import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.control.heuristics.lambert.LambertSolution;
import org.orekit.frames.Frame;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeUtils;
import org.orekit.utils.TimeStampedPVCoordinates;

/**
 * This class stores the solution of a two-impulse rendez-vous. All the contained variables are expressed in the same
 * frame, accessible through the method getFrame(). Since the PVT of the chaser on both ends of the transfer orbit are
 * stored in this object in addition to the ΔV vectors, it is possible to reconstruct the orbit of the chaser before and
 * after the transfer.
 *
 * @author Jérôme Tabeaud
 * @author Romain Cuvillon
 * @since 14.0
 */
public class TwoImpulseTransfer {

    /**
     * PVT of the chaser just after the first maneuver.
     */
    private final TimeStampedPVCoordinates pvt1;

    /**
     * PVT of the chaser just before the second maneuver.
     */
    private final TimeStampedPVCoordinates pvt2;

    /**
     * ΔV vector of first maneuver.
     */
    private final Vector3D deltaV1;

    /**
     * ΔV vector of second maneuver.
     */
    private final Vector3D deltaV2;

    /**
     * Frame in which the PVT and ΔV are expressed.
     */
    private final Frame frame;

    /**
     * Creates a new TwoImpulseTransfer object from the given PVT and ΔV vectors.
     *
     * @param pvt1    PVT at the start of the transfer
     * @param pvt2    PVT at the end of the transfer
     * @param deltaV1 ΔV to enter the transfer orbit
     * @param deltaV2 ΔV to exit the transfer orbit
     * @param frame   Frame in which the PVT and ΔV vector are expressed
     */
    public TwoImpulseTransfer(final TimeStampedPVCoordinates pvt1,
                              final TimeStampedPVCoordinates pvt2,
                              final Vector3D deltaV1,
                              final Vector3D deltaV2,
                              final Frame frame) {
        this.pvt1    = pvt1;
        this.pvt2    = pvt2;
        this.deltaV1 = deltaV1;
        this.deltaV2 = deltaV2;
        this.frame   = frame;
    }

    /**
     * <p>Creates a new {@link TwoImpulseTransfer} object from a {@link LambertSolution} and the PVs from the departure
     * and destination orbits.</p>
     * <p>The ΔV vectors are automatically computed.</p>
     *
     * @param solution    Lambert solver's solution
     * @param v1BeforeMan PV before departure manoeuvre on initial orbit
     * @param v2AfterMan  PV after arrival manoeuvre on destination orbit
     */
    public TwoImpulseTransfer(final LambertSolution solution, final Vector3D v1BeforeMan, final Vector3D v2AfterMan) {
        final TimeStampedPVCoordinates pvt1AfterMan =
                        new TimeStampedPVCoordinates(solution.getBoundaryConditions().getInitialDate(),
                                                     solution.getBoundaryConditions().getInitialPosition(),
                                                     solution.getBoundaryVelocities().getInitialVelocity());
        final TimeStampedPVCoordinates pvt2BeforeMan =
                        new TimeStampedPVCoordinates(solution.getBoundaryConditions().getTerminalDate(),
                                                     solution.getBoundaryConditions().getTerminalPosition(),
                                                     solution.getBoundaryVelocities().getTerminalVelocity());
        this.pvt1    = pvt1AfterMan;
        this.pvt2    = pvt2BeforeMan;
        this.deltaV1 = pvt1AfterMan.getVelocity().subtract(v1BeforeMan);
        this.deltaV2 = pvt2BeforeMan.getVelocity().subtract(v2AfterMan);
        this.frame   = solution.getBoundaryConditions().getReferenceFrame();
    }

    /**
     * Creates a new {@link TwoImpulseTransfer} object from the given PVT before the first maneuver and after the second
     * maneuver (on the initial and final orbits), as well as the velocity vectors after the first maneuver and before
     * the second maneuver (on the transfer orbit).
     *
     * @param pvt1BeforeMan PVT before the departure maneuver
     * @param v1AfterMan    Velocity after the departure maneuver
     * @param pvt2AfterMan  PVT after the rendez-vous maneuver
     * @param v2BeforeMan   Velocity before the rendez-vous maneuver
     * @param frame         Frame in which the PVT and velocity vectors are expressed
     * @return TwoImpulseTransfer object that corresponds to the inputs
     */
    public static TwoImpulseTransfer fromPVTAndVelocities(final TimeStampedPVCoordinates pvt1BeforeMan,
                                                          final Vector3D v1AfterMan,
                                                          final TimeStampedPVCoordinates pvt2AfterMan,
                                                          final Vector3D v2BeforeMan,
                                                          final Frame frame) {
        final TimeStampedPVCoordinates pvt1AfterMan =
                        new TimeStampedPVCoordinates(pvt1BeforeMan.getDate(), pvt1BeforeMan.getPosition(), v1AfterMan);
        final TimeStampedPVCoordinates pvt2BeforeMan =
                        new TimeStampedPVCoordinates(pvt2AfterMan.getDate(), pvt2AfterMan.getPosition(), v2BeforeMan);
        final Vector3D deltaV1 = pvt1AfterMan.getVelocity().subtract(pvt1BeforeMan.getVelocity());
        final Vector3D deltaV2 = pvt2AfterMan.getVelocity().subtract(pvt2BeforeMan.getVelocity());
        return new TwoImpulseTransfer(pvt1AfterMan, pvt2BeforeMan, deltaV1, deltaV2, frame);
    }

    /**
     * Get the PVT of the chaser just after the first maneuver.
     *
     * @return PVT of the chaser just after the first maneuver
     */
    public TimeStampedPVCoordinates getPvt1() {
        return pvt1;
    }

    /**
     * Get PVT of the chaser just before the second maneuver.
     *
     * @return PVT of the chaser just before the second maneuver
     */
    public TimeStampedPVCoordinates getPvt2() {
        return pvt2;
    }

    /**
     * Get the ΔV vector of first maneuver.
     *
     * @return The ΔV vector of first maneuver
     */
    public Vector3D getDeltaV1() {
        return deltaV1;
    }

    /**
     * Get the ΔV vector of second maneuver.
     *
     * @return ΔV vector of second maneuver
     */
    public Vector3D getDeltaV2() {
        return deltaV2;
    }

    /**
     * Get PVT of the chaser just after the first maneuver.
     *
     * @param outputFrame given frame for the first maneuver
     * @return PVT of the chaser just after the first maneuver
     */
    public TimeStampedPVCoordinates getPvt1(final Frame outputFrame) {
        return frame.getTransformTo(outputFrame, pvt1.getDate()).transformPVCoordinates(pvt1);
    }

    /**
     * Get PVT of the chaser just before the second maneuver.
     *
     * @param outputFrame given frame for the 2nd maneuver
     * @return PVT of the chaser just before the second maneuver
     */
    public TimeStampedPVCoordinates getPvt2(final Frame outputFrame) {
        return frame.getTransformTo(outputFrame, pvt2.getDate()).transformPVCoordinates(pvt2);
    }

    /**
     * Get the ΔV vector of first maneuver in the given frame.
     *
     * @param outputFrame given frame for the first maneuver
     * @return The ΔV vector of first maneuver in the given frame at the given date
     */
    public Vector3D getDeltaV1(final Frame outputFrame) {
        return frame.getTransformTo(outputFrame, pvt1.getDate()).transformVector(deltaV1);
    }

    /**
     * Get the ΔV vector of second maneuver in the given frame at the given date.
     *
     * @param outputFrame given frame for the 2nd maneuver
     * @return The ΔV vector of second maneuver in the given frame at the given date
     */
    public Vector3D getDeltaV2(final Frame outputFrame) {
        return frame.getTransformTo(outputFrame, pvt2.getDate()).transformVector(deltaV2);
    }

    /**
     * Computes the total ΔV of the transfer.
     *
     * @return ||ΔV_total|| = ||ΔV1|| + ||ΔV2||
     */
    public double getTotalDeltaV() {
        return deltaV1.getNorm() + deltaV2.getNorm();
    }

    /**
     * Get the duration of the transfer between the two impulses.
     *
     * @return The duration of the transfer between the two impulses
     */
    public double getDuration() {
        return pvt2.durationFrom(pvt1);
    }

    /**
     * Returns the departure date.
     *
     * @return the departure date
     */
    public AbsoluteDate getDepartureDate() {
        return pvt1.getDate();
    }

    /**
     * Returns the arrival date.
     *
     * @return the arrival date
     */
    public AbsoluteDate getArrivalDate() {
        return pvt2.getDate();
    }

    /**
     * Returns the frame in which all elements contained in the object are expressed.
     *
     * @return The frame of the elements of the object
     */
    public Frame getFrame() {
        return frame;
    }

    /**
     * Returns the PVT of the chaser just before the injection maneuver into the transfer orbit.
     * @return The PVT before the injection maneuver into the transfer orbit
     */
    public TimeStampedPVCoordinates getPvt1BeforeMan() {
        return new TimeStampedPVCoordinates(pvt1.getDate(), pvt1.getPosition(), pvt1.getVelocity().subtract(deltaV1));
    }

    /**
     * Returns the PVT of the chaser just after the velocity-synchronization maneuver into the target orbit.
     * @return The PVT after the velocity-synchronization maneuver into the target orbit
     */
    public TimeStampedPVCoordinates getPvt2AfterMan() {
        return new TimeStampedPVCoordinates(pvt2.getDate(), pvt2.getPosition(), pvt2.getVelocity().add(deltaV2));
    }

    /**
     * Converts all the PVT and ΔV vectors to the desired frame.
     *
     * @param outputFrame Desired output frame
     * @return TwoImpulseTransfer object expressed in the desired frame
     */
    public TwoImpulseTransfer expressInFrame(final Frame outputFrame) {
        return new TwoImpulseTransfer(getPvt1(outputFrame),
                                      getPvt2(outputFrame),
                                      getDeltaV1(outputFrame),
                                      getDeltaV2(outputFrame),
                                      outputFrame);
    }

    /**
     * Writes a string summarizing the transfer characteristics (PVT and ΔV vectors).
     *
     * @return The string summary
     */
    public String toString() {
        return "Two impulse transfer\n" +
               "\n    Frame:                       " + getFrame().getName() +
               "\n    PVT after first manoeuvre:   " + getPvt1() +
               "\n    PVT before second manoeuvre: " + getPvt2() +
               "\n    ΔV of first manoeuvre:       " + getDeltaV1() + " ; ‖ΔV₁‖ = " + getDeltaV1().getNorm() +
               "\n    ΔV of second manoeuvre:      " + getDeltaV2() + " ; ‖ΔV₂‖ = " + getDeltaV2().getNorm() +
               "\n    Total ΔV:                    " + getTotalDeltaV() +
               "\n    Duration:                    " + TimeUtils.secondsToDHMS(getDuration()) + "\n";
    }
}