IodLambert.java

  1. /* Copyright 2002-2018 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.estimation.iod;

  18. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  19. import org.hipparchus.util.FastMath;
  20. import org.orekit.frames.Frame;
  21. import org.orekit.orbits.KeplerianOrbit;
  22. import org.orekit.time.AbsoluteDate;
  23. import org.orekit.utils.PVCoordinates;

  24. /**
  25.  * Lambert initial orbit determination, assuming Keplerian motion.
  26.  * An orbit is determined from two position vectors.
  27.  *
  28.  * References:
  29.  *  Battin, R.H., An Introduction to the Mathematics and Methods of Astrodynamics, AIAA Education, 1999.
  30.  *  Lancaster, E.R. and Blanchard, R.C., A Unified Form of Lambert’s Theorem, Goddard Space Flight Center, 1968.
  31.  *
  32.  * @author Joris Olympio
  33.  * @since 8.0
  34.  */
  35. public class IodLambert {

  36.     /** gravitational constant. */
  37.     private final double mu;

  38.     /** Creator.
  39.      *
  40.      * @param mu gravitational constant
  41.      */
  42.     public IodLambert(final double mu) {
  43.         this.mu = mu;
  44.     }

  45.     /** Estimate a Keplerian orbit given two position vectors and a duration.
  46.      * <p>
  47.      * The logic for setting {@code posigrade} and {@code nRev} is that the
  48.      * sweep angle Δυ travelled by the object between {@code t1} and {@code t2} is
  49.      * 2π {@code nRev} - α if {@code posigrade} is false and 2π {@code nRev} + α
  50.      * if {@code posigrade} is true, where α is the separation angle between
  51.      * {@code p1} and {@code p2}, which is always computed between 0 and π
  52.      * (because in 3D without a normal reference, vector angles cannot go past π).
  53.      * </p>
  54.      * <p>
  55.      * This implies that {@code posigrade} should be set to true if {@code p2} is
  56.      * located in the half orbit starting at {@code p1} and it should be set to
  57.      * false if {@code p2} is located in the half orbit ending at {@code p1},
  58.      * regardless of the number of periods between {@code t1} and {@code t2},
  59.      * and {@code nRev} should be set accordingly.
  60.      * </p>
  61.      * <p>
  62.      * As an example, if {@code t2} is less than half a period after {@code t1},
  63.      * then {@code posigrade} should be {@code true} and {@code nRev} should be 0.
  64.      * If {@code t2} is more than half a period after {@code t1} but less than
  65.      * one period after {@code t1}, {@code posigrade} should be {@code false} and
  66.      * {@code nRev} should be 1.
  67.      * </p>
  68.      * @param frame     frame
  69.      * @param posigrade flag indicating the direction of motion
  70.      * @param nRev      number of revolutions
  71.      * @param p1        position vector 1
  72.      * @param t1        date of observation 1
  73.      * @param p2        position vector 2
  74.      * @param t2        date of observation 2
  75.      * @return  an initial Keplerian orbit estimate
  76.      */
  77.     public KeplerianOrbit estimate(final Frame frame, final boolean posigrade,
  78.                                    final int nRev,
  79.                                    final Vector3D p1, final AbsoluteDate t1,
  80.                                    final Vector3D p2, final AbsoluteDate t2) {

  81.         final double r1 = p1.getNorm();
  82.         final double r2 = p2.getNorm();
  83.         final double tau = t2.durationFrom(t1); // in seconds

  84.         // normalizing constants
  85.         final double R = FastMath.max(r1, r2); // in m
  86.         final double V = FastMath.sqrt(mu / R);  // in m/s
  87.         final double T = R / V; // in seconds

  88.         // sweep angle
  89.         double dth = Vector3D.angle(p1, p2);
  90.         // compute the number of revolutions
  91.         if (!posigrade) {
  92.             dth = 2 * FastMath.PI - dth;
  93.         }
  94.         dth = dth + nRev * 2 * FastMath.PI;

  95.         // velocity vectors in the orbital plane, in the R-T frame
  96.         final double[] Vdep = new double[2];

  97.         // call Lambert's problem solver
  98.         final boolean exitflag = solveLambertPb(r1 / R, r2 / R, dth, tau / T, nRev, Vdep);

  99.         if (exitflag) {
  100.             // basis vectors
  101.             // normal to the orbital arc plane
  102.             final Vector3D Pn = p1.crossProduct(p2);
  103.             // perpendicular to the radius vector, in the orbital arc plane
  104.             final Vector3D Pt = Pn.crossProduct(p1);

  105.             // tangential velocity norm
  106.             double RT = Pt.getNorm();
  107.             if (!posigrade) {
  108.                 RT = -RT;
  109.             }

  110.             // velocity vector at P1
  111.             final Vector3D Vel1 = new Vector3D(V * Vdep[0] / r1, p1,
  112.                                                V * Vdep[1] / RT, Pt);

  113.             // compute the equivalent Keplerian orbit
  114.             return new KeplerianOrbit(new PVCoordinates(p1, Vel1), frame, t1, mu);
  115.         }

  116.         return null;
  117.     }

  118.     /** Lambert's solver.
  119.      * Assume mu=1.
  120.      *
  121.      * @param r1 radius 1
  122.      * @param r2  radius 2
  123.      * @param dth sweep angle
  124.      * @param tau time of flight
  125.      * @param mRev number of revs
  126.      * @param V1 velocity at departure in (T, N) basis
  127.      * @return something
  128.      */
  129.     boolean solveLambertPb(final double r1, final double r2, final double dth, final double tau,
  130.                            final int mRev, final double[] V1) {
  131.         // decide whether to use the left or right branch (for multi-revolution
  132.         // problems), and the long- or short way.
  133.         final boolean leftbranch = FastMath.signum(mRev) > 0;
  134.         int longway = 0;
  135.         if (tau > 0) {
  136.             longway = 1;
  137.         }

  138.         final int m = FastMath.abs(mRev);
  139.         final double rtof = FastMath.abs(tau);
  140.         double theta = dth;
  141.         if (longway < 0) {
  142.             theta = 2 * FastMath.PI - dth;
  143.         }

  144.         // non-dimensional chord ||r2-r1||
  145.         final double chord = FastMath.sqrt(r1 * r1 + r2 * r2 - 2 * r1 * r2 * FastMath.cos(theta));

  146.         // non-dimensional semi-perimeter of the triangle
  147.         final double speri = 0.5 * (r1 + r2 + chord);

  148.         // minimum energy ellipse semi-major axis
  149.         final double minSma = speri / 2.;

  150.         // lambda parameter (Eq 7.6)
  151.         final double lambda = FastMath.sqrt(1 - chord / speri);

  152.         // reference tof value for the Newton solver
  153.         final double logt = FastMath.log(rtof);

  154.         // initialisation of the iterative root finding process (secant method)
  155.         // initial values
  156.         //  -1 < x < 1  =>  Elliptical orbits
  157.         //  x = 1           Parabolic orbit
  158.         //  x > 1           Hyperbolic orbits
  159.         final double in1;
  160.         final double in2;
  161.         double x1;
  162.         double x2;
  163.         if (m == 0) {
  164.             // one revolution, one solution. Define the left and right asymptotes.
  165.             in1 = -0.6523333;
  166.             in2 = 0.6523333;
  167.             x1   = FastMath.log(1 + in1);
  168.             x2   = FastMath.log(1 + in2);
  169.         } else {
  170.             // select initial values, depending on the branch
  171.             if (!leftbranch) {
  172.                 in1 = -0.523334;
  173.                 in2 = -0.223334;
  174.             } else {
  175.                 in1 = 0.723334;
  176.                 in2 = 0.523334;
  177.             }
  178.             x1 = FastMath.tan(in1 * 0.5 * FastMath.PI);
  179.             x2 = FastMath.tan(in2 * 0.5 * FastMath.PI);
  180.         }

  181.         // initial estimates for the tof
  182.         final double tof1 = timeOfFlight(in1, longway, m, minSma, speri, chord);
  183.         final double tof2 = timeOfFlight(in2, longway, m, minSma, speri, chord);

  184.         // initial bounds for y
  185.         double y1;
  186.         double y2;
  187.         if (m == 0) {
  188.             y1 = FastMath.log(tof1) - logt;
  189.             y2 = FastMath.log(tof2) - logt;
  190.         } else {
  191.             y1 = tof1 - rtof;
  192.             y2 = tof2 - rtof;
  193.         }

  194.         // Solve for x with the secant method
  195.         double err = 1e20;
  196.         int iterations = 0;
  197.         final double tol = 1e-13;
  198.         final int maxiter = 50;
  199.         double xnew = 0;
  200.         while ((err > tol) && (iterations < maxiter)) {
  201.             // new x
  202.             xnew = (x1 * y2 - y1 * x2) / (y2 - y1);

  203.             // evaluate new time of flight
  204.             final double x;
  205.             if (m == 0) {
  206.                 x = FastMath.exp(xnew) - 1;
  207.             } else {
  208.                 x = FastMath.atan(xnew) * 2 / FastMath.PI;
  209.             }

  210.             final double tof = timeOfFlight(x, longway, m, minSma, speri, chord);

  211.             // new value of y
  212.             final double ynew;
  213.             if (m == 0) {
  214.                 ynew = FastMath.log(tof) - logt;
  215.             } else {
  216.                 ynew = tof - rtof;
  217.             }

  218.             // save previous and current values for the next iteration
  219.             x1 = x2;
  220.             x2 = xnew;
  221.             y1 = y2;
  222.             y2 = ynew;

  223.             // update error
  224.             err = FastMath.abs(x1 - xnew);

  225.             // increment number of iterations
  226.             ++iterations;
  227.         }

  228.         // failure to converge
  229.         if (err > tol) {
  230.             return false;
  231.         }

  232.         // convert converged value of x
  233.         final double x;
  234.         if (m == 0) {
  235.             x = FastMath.exp(xnew) - 1;
  236.         } else {
  237.             x = FastMath.atan(xnew) * 2 / FastMath.PI;
  238.         }

  239.         // Solution for the semi-major axis (Eq. 7.20)
  240.         final double sma = minSma / (1 - x * x);

  241.         // compute velocities
  242.         final double eta;
  243.         if (x < 1) {
  244.             // ellipse, Eqs. 7.7, 7.17
  245.             final double alfa = 2 * FastMath.acos(x);
  246.             final double beta = longway * 2 * FastMath.asin(FastMath.sqrt((speri - chord) / (2. * sma)));
  247.             final double psi  = (alfa - beta) / 2;
  248.             // Eq. 7.21
  249.             final double sinPsi = FastMath.sin(psi);
  250.             final double etaSq = 2 * sma * sinPsi * sinPsi / speri;
  251.             eta  = FastMath.sqrt(etaSq);
  252.         } else {
  253.             // hyperbola
  254.             final double gamma = 2 * FastMath.acosh(x);
  255.             final double delta = longway * 2 * FastMath.asinh(FastMath.sqrt((chord - speri) / (2 * sma)));
  256.             //
  257.             final double psi  = (gamma - delta) / 2.;
  258.             final double sinhPsi = FastMath.sinh(psi);
  259.             final double etaSq = -2 * sma * sinhPsi * sinhPsi / speri;
  260.             eta  = FastMath.sqrt(etaSq);
  261.         }

  262.         // radial and tangential directions for departure velocity (Eq. 7.36)
  263.         final double VR1 = (1. / eta) * FastMath.sqrt(1. / minSma) * (2 * lambda * minSma / r1 - (lambda + x * eta));
  264.         final double VT1 = (1. / eta) * FastMath.sqrt(1. / minSma) * FastMath.sqrt(r2 / r1) * FastMath.sin(dth / 2);
  265.         V1[0] = VR1;
  266.         V1[1] = VT1;

  267.         return true;
  268.     }

  269.     /** Compute the time of flight of a given arc of orbit.
  270.      * The time of flight is evaluated via the Lagrange expression.
  271.      *
  272.      * @param x          x
  273.      * @param longway    solution number; the long way or the short war
  274.      * @param mrev       number of revolutions of the arc of orbit
  275.      * @param minSma     minimum possible semi-major axis
  276.      * @param speri      semi-parameter of the arc of orbit
  277.      * @param chord      chord of the arc of orbit
  278.      * @return the time of flight for the given arc of orbit
  279.      */
  280.     private double timeOfFlight(final double x, final int longway, final int mrev, final double minSma,
  281.                                 final double speri, final double chord) {

  282.         final double a = minSma / (1 - x * x);

  283.         final double tof;
  284.         if (FastMath.abs(x) < 1) {
  285.             // Lagrange form of the time of flight equation Eq. (7.9)
  286.             // elliptical orbit (note: mu = 1)
  287.             final double beta = longway * 2 * FastMath.asin(FastMath.sqrt((speri - chord) / (2. * a)));
  288.             final double alfa = 2 * FastMath.acos(x);
  289.             tof = a * FastMath.sqrt(a) * ((alfa - FastMath.sin(alfa)) - (beta - FastMath.sin(beta)) + 2 * FastMath.PI * mrev);
  290.         } else {
  291.             // hyperbolic orbit
  292.             final double alfa = 2 * FastMath.acosh(x);
  293.             final double beta = longway * 2 * FastMath.asinh(FastMath.sqrt((speri - chord) / (-2. * a)));
  294.             tof = -a * FastMath.sqrt(-a) * ((FastMath.sinh(alfa) - alfa) - (FastMath.sinh(beta) - beta));
  295.         }

  296.         return tof;
  297.     }
  298. }