Example #1
0
/**
 * Construct a planet_tle from two strings containing the two line elements
 * \param[in] line1 first line
 * \param[in] line2 second line
 */
tle::tle(const std::string& line1, const std::string& line2)
try : base(), m_line1(line1), m_line2(line2), m_tle(Tle("TLE satellite", line1, line2)), m_sgp4_propagator(SGP4(m_tle))
{
	// We read the osculating elements of the satellite
	array6D keplerian_elements;
	double mu_central_body = kMU*1E09; // (m^3/s^2)
	double mean_motion = m_tle.MeanMotion() * 2 * kPI / kSECONDS_PER_DAY; // [rad/s]
	keplerian_elements[0] = std::pow(mu_central_body / (mean_motion*mean_motion),1./3); // a [m]
	keplerian_elements[1] = m_tle.Eccentricity(); // e
	keplerian_elements[2] = m_tle.Inclination(false); // i [rad]
	keplerian_elements[3] = m_tle.RightAscendingNode(false); // Om [rad]
	keplerian_elements[4] = m_tle.ArgumentPerigee(false); // om [rad]
	keplerian_elements[5] = m_tle.MeanAnomaly(false); // M [rad]

    std::string year_str = m_tle.IntDesignator().substr(0,2);
    int year_int = std::stoi(year_str);
    std::string rest = m_tle.IntDesignator().substr(2);
    int prefix = (year_int > 50)?(19):(20); 

    std::string object_name(std::to_string(prefix)+year_str+std::string("-")+rest);
	set_mu_central_body(mu_central_body);
	set_name(object_name);
	m_ref_mjd2000 = epoch(m_tle.Epoch().ToJulian(),epoch::JD).mjd2000();

}
catch (TleException& e)
{
		std::cout << "TleException cought in planet_tle constructor" << std::endl;
		throw_value_error(e.what());
}	
catch (SatelliteException& e)
{
		std::cout << "SatelliteException cought in planet_tle constructor" << std::endl;
		throw_value_error(e.what());
}
Example #2
0
	void TleGen( Vector6 randKepElem, std::string& SolverStatus, int& IterationCount )
	{
		// grav. parameter 'mu' of earth
    	const double muEarth = kMU*( pow( 10, 9 ) ); // unit m^3/s^2
	    // generate sets of cartesian elements corresponding to each of the psuedo random orbital element set. 
	    // The conversion from keplerian elements to the cartesian elements is done using the PyKep library from ESA.
	    Vector3 CartPos( 3 ); // empty vector to store position coordinates
	    Vector3 CartVel( 3 ); // empty vector to store velocity components
	    // Real rangeMag = 0; // magnitude range
	    // Real velocityMag = 0; // velocity magnitude
	    kep_toolbox::par2ic( randKepElem, muEarth, CartPos, CartVel );
	   
	    // verification of the conversion process using Ron Noomen's lecture notes from TUDelft
	    // Document ID ae4878.basics.v4-16.pdf
	    // Vector6 testKep = { 6787746.891, 0.000731104, sml::convertDegreesToRadians( 51.68714486 ), 
	    //     sml::convertDegreesToRadians( 127.5486706 ), sml::convertDegreesToRadians( 74.21987137 ), sml::convertDegreesToRadians( 24.08317766 ) };
	    // Vector3 testPos( 3 );
	    // Vector3 testVel( 3 );
	    // kep_toolbox::par2ic( testKep, muEarth, testPos, testVel );    
	    // std::cout << testPos[ 0 ] << std::endl;
	    // std::cout << testPos[ 1 ] << std::endl;
	    // std::cout << testPos[ 2 ] << std::endl;
	    // std::cout << testVel[ 0 ] << std::endl;
	    // std::cout << testVel[ 1 ] << std::endl;
	    // std::cout << testVel[ 2 ] << std::endl;

	    // convert the cartesian elements to the corresponding TLE format using the ATOM toolbox
	    Vector6 cartesianState( 6 );
	    // cartesianState[ 0 ] = -7.1e3;
	    // cartesianState[ 1 ] = 2.7e3;
	    // cartesianState[ 2 ] = 1.3e3;
	    // cartesianState[ 3 ] = -2.5;
	    // cartesianState[ 4 ] = -5.5;
	    // cartesianState[ 5 ] = 5.5;
	    Tle convertedTle;
	    Tle referenceTle = Tle(); // empty TLE for reference
	    // std::cout << referenceTle << std::endl << std::endl;
	    const Real absTol = 1.0e-10; // absolute tolerance
	    const Real relTol = 1.0e-5; // relative tolerance
	    const int maxItr = 100; // maximum allowed iterations per conversion run
	    
	    // important note, the atom function converting cartesian to TLEs takes in values in km and km/s.
	    cartesianState[ 0 ] = CartPos[ 0 ]/1000;
	    cartesianState[ 1 ] = CartPos[ 1 ]/1000;
	    cartesianState[ 2 ] = CartPos[ 2 ]/1000;
	    cartesianState[ 3 ] = CartVel[ 0 ]/1000;
	    cartesianState[ 4 ] = CartVel[ 1 ]/1000;
	    cartesianState[ 5 ] = CartVel[ 2 ]/1000;
		convertedTle = atom::convertCartesianStateToTwoLineElements< Real, Vector6 >( cartesianState, DateTime( ), SolverStatus, 
	    	IterationCount, referenceTle, kMU, kXKMPER, absTol, relTol, maxItr );
	}
namespace atom
{

const static int meanMotionIndex  = astro::semiMajorAxisIndex;
const static int meanAnomalyIndex = astro::trueAnomalyIndex;

//! Convert Cartesian state to TLE (Two Line Elements).
/*!
 * Converts a given Cartesian state (position, velocity) to an equivalent TLE.
 *
 * This function makes use of a root-finder to solve a non-linear system. Locating the root of the
 * non-linear system corresponds finding the TLE that, when evaluated at its epoch using the
 * SGP4/SDP4 propagator (Vallado, 2012), yields the target Cartesian state (within tolerance).
 *
 * Details of the underlying non-linear system and algorithm are catalogued by
 * Kumar, et al. (2014).
 *
 * @sa     evaluateCartesianToTwoLineElementsSystem, DateTime
 * @tparam Real                        Type for reals
 * @tparam Vector6                     Type for 6-vector of reals
 * @param  cartesianState              Cartesian state [km; km/s]
 * @param  epoch                       Epoch associated with Cartesian state, stored in a
 *                                     DateTime object
 * @param  solverStatusSummary         Status of non-linear solver printed as a table
 * @param  numberOfIterations          Number of iterations completed by solver
 * @param  referenceTle                Reference Two Line Elements. This is used as reference to
 *                                     construct the effective TLE for the given Cartesian state
 *                                     [default: 0-TLE].
 * @param  earthGravitationalParameter Earth gravitational parameter [km^3 s^-2] [default: mu_SGP]
 * @param  earthMeanRadius             Earth mean radius [km] [default: R_SGP]
 * @param  absoluteTolerance           Absolute tolerance used to check if root-finder has
 *                                     converged [default: 1.0e-10] (see Kumar, et al. (2014) for
 *                                     details on how convergence is tested)
 * @param  relativeTolerance           Relative tolerance used to check if root-finder has
 *                                     converged [default: 1.0e-5] (see Kumar, et al. (2014) for
 *                                     details on how convergence is tested)
 * @param  maximumIterations           Maximum number of solver iterations permitted. Once the
 *                                     solver reaches this limit, the loop will be broken and the
 *                                     solver status will report that it has not converged
 *                                     [default: 100].
 * @return                             TLE object that generates target Cartesian state when
 *                                     propagated with SGP4 propagator to target epoch
 */
template< typename Real, typename Vector6 >
const Tle convertCartesianStateToTwoLineElements(
    const Vector6& cartesianState,
    const DateTime& epoch,
    std::string& solverStatusSummary,
    int& numberOfIterations,
    const Tle& referenceTle = Tle( ),
    const Real earthGravitationalParameter = kMU,
    const Real earthMeanRadius = kXKMPER,
    const Real absoluteTolerance = 1.0e-10,
    const Real relativeTolerance = 1.0e-8,
    const int maximumIterations = 100 );

//! Convert Cartesian state to TLE (Two Line Elements).
/*!
 * Converts a given Cartesian state (position, velocity) to an equivalent TLE.
 *
 * This function makes use of a root-finder to solve a non-linear system. Locating the root of the
 * non-linear system corresponds finding the TLE that, when evaluated at its epoch using the
 * SGP4/SDP4 propagator (Vallado, 2012), yields the target Cartesian state (within tolerance).
 *
 * Details of the underlying non-linear system and algorithm are catalogued by
 * Kumar, et al. (2014).
 *
 * This is a function overload to ensure that the user can opt to leave out solver summary status
 * string and number of iterations counter from the call (overload is necessary since non-const
 * references cannot be assigned default values in C++).
 *
 * @sa     convertCartesianStateToTwoLineElements, evaluateCartesianToTwoLineElementsSystem,
 *         DateTime
 * @tparam Real                        Type for reals
 * @tparam Vector6                     Type for 6-vector of reals
 * @param  cartesianState              Cartesian state [km; km/s]
 * @param  epoch                       Epoch associated with Cartesian state, stored in a
 *                                     DateTime object
 * @return                             TLE object that generates target Cartesian state when
 *                                     propagated with SGP4 propagator to target epoch
 */
template< typename Real, typename Vector6 >
const Tle convertCartesianStateToTwoLineElements( const Vector6& cartesianState,
                                                  const DateTime& epoch );

//! Compute residuals for converting Cartesian state to TLE.
/*!
 * Evaluates system of non-linear equations and computes residuals to find TLE corresponding with
 * target Cartesian state. The residual function, \f$\bar{R}\f$ is computed as follows:
 *  \f[
 *      \bar{R} = 0 = \begin{pmatrix}
 *                      \frac{\bar{r}_{new} - \bar{r}_{target}}{R_{Earth}}\\
 *                      \frac{\bar{v}_{new} - \bar{v}_{target}}{V_{c,Earth}}\\
 *                    \end{pmatrix}
 *  \f]
 * where \f$\bar{r}_{new}\f$ and \f$\bar{v}_{new}\f$ are the new Cartesian position and velocity
 * vectors, computed by updating the TLE mean elements and propagating the TLE using the SGP4
 * propagator, \f$\bar{r}_{target}\f$ anf \f$\bar{v}_{target}\f$ are the target Cartesian position
 * and velocity vectors, \f$R_{Earth}\f$ is the mean radius of the Earth, and \f$V_{c,Earth}\f$ is
 * the circular velocity at \f$R_{Earth}\f$. Note that the residuals are non-dimensional. They are
 * used to drive a root-finding process that uses the GSL library.
 *
 * @sa convertCartesianStateToTwoLineElements
 * @tparam Real                 Type for reals
 * @tparam Vector6              Type for 6-vector of reals
 * @param  independentVariables Vector of independent variables used by the root-finder
 * @param  parameters           Parameters required to compute the objective function
 * @param  residuals            Vector of computed residuals
 * @return                      GSL flag indicating success or failure
 */
template< typename Real, typename Vector6 >
int computeCartesianToTwoLineElementResiduals( const gsl_vector* independentVariables,
                                               void* parameters,
                                               gsl_vector* residuals );

//! Compute initial guess for TLE mean elements.
/*
 * Computes initial guess for TLE mean elements by converting Keplerian elements provided by user to
 * TLE mean elements. The TLE mean elements generated are to floating-point precision, since the
 * TLE class stores the internal values as doubles.
 *
 * @sa Tle
 * @tparam Real                         Type for reals
 * @tparam Vector6                      Type for 6-vector of reals
 * @param  keplerianElements            Keplerian elements to be used as initial guess
 *                                      The order of Keplerian elements in the vector is:
 *                                      - semi-major axis                                      [km]
 *                                      - eccentricity                                          [-]
 *                                      - inclination                                         [rad]
 *                                      - argument of periapsis                               [rad]
 *                                      - right ascension of ascending node                   [rad]
 *                                      - true anomaly                                        [rad]
 * @param  earthGravitationalParameter  Earth gravitational parameter                   [km^3 s^-2]
 * @return                              6-vector containing initial guess for TLE mean
 *                                      elements
 *                                      The order of mean TLE elements in the vector is:
 *                                      - mean inclination                                    [deg]
 *                                      - mean right ascension of ascending node              [deg]
 *                                      - mean eccentricity                                     [-]
 *                                      - mean argument of perigee                            [deg]
 *                                      - mean mean anomaly                                   [deg]
 *                                      - mean mean motion                                [rev/day]
 */
template< typename Real, typename Vector6 >
const Vector6 computeInitialGuessTleMeanElements( const Vector6& keplerianElements,
                                                  const Real earthGravitationalParameter );

//! Parameter struct used by Cartesian-to-TLE residual function.
/*!
 * Data structure with parameters used to compute Cartesian-to-TLE residual function.
 *
 * @sa computeCartesianToTwoLineElementResiduals
 * @tparam Vector6 Type for 6-vector of reals
 */
template< typename Vector6 >
struct CartesianToTwoLineElementsParameters;

//! Convert Cartesian state to TLE (Two Line Elements).
template< typename Real, typename Vector6 >
const Tle convertCartesianStateToTwoLineElements(
    const Vector6& cartesianState,
    const DateTime& epoch,
    std::string& solverStatusSummary,
    int& numberOfIterations,
    const Tle& referenceTle,
    const Real earthGravitationalParameter,
    const Real earthMeanRadius,
    const Real absoluteTolerance,
    const Real relativeTolerance,
    const int maximumIterations )
{
    // Store reference TLE as the template TLE and update epoch.
    Tle templateTle = referenceTle;
    templateTle.updateEpoch( epoch );

    // Set up parameters for residual function.
    CartesianToTwoLineElementsParameters< Vector6 > parameters( cartesianState, templateTle );

    // Set up residual function.
    gsl_multiroot_function cartesianToTwoLineElementsFunction
        = { &computeCartesianToTwoLineElementResiduals< Real, Vector6 >,
            6,
            &parameters };

    // Compute current state in Keplerian elements, for use as initial guess for the TLE mean
    // elements.
    const Vector6 initialKeplerianElements = astro::convertCartesianToKeplerianElements(
        parameters.targetState, earthGravitationalParameter );

    // Compute initial guess for TLE mean elements.
    const Vector6 initialTleMeanElements
        = computeInitialGuessTleMeanElements( initialKeplerianElements,
                                              earthGravitationalParameter );

    // Set initial guess.
    gsl_vector* initialGuessTleMeanElements = gsl_vector_alloc( 6 );
    for ( int i = 0; i < 6; i++ )
    {
        gsl_vector_set( initialGuessTleMeanElements, i, initialTleMeanElements[ i ] );
    }

    // Set up solver type (derivative free).
    const gsl_multiroot_fsolver_type* solverType = gsl_multiroot_fsolver_hybrids;

    // Allocate memory for solver.
    gsl_multiroot_fsolver* solver = gsl_multiroot_fsolver_alloc( solverType, 6 );

    // Set solver to use residual function with initial guess for TLE mean elements.
    gsl_multiroot_fsolver_set( solver,
                               &cartesianToTwoLineElementsFunction,
                               initialGuessTleMeanElements );

     // Declare current solver status and iteration counter.
    int solverStatus = false;
    int counter = 0;

    // Set up buffer to store solver status summary table.
    std::ostringstream summary;

    // Print header for summary table to buffer.
    summary << printCartesianToTleSolverStateTableHeader( );

    do
    {
        // Print current state of solver for summary table.
        summary << printCartesianToTleSolverState( counter, solver );

        // Increment iteration counter.
        ++counter;

        // Execute solver iteration.
        solverStatus = gsl_multiroot_fsolver_iterate( solver );

        // Check if solver is stuck; if it is stuck, break from loop.
        if ( solverStatus )
        {
            solverStatusSummary = summary.str( );
            throw std::runtime_error( "ERROR: Non-linear solver is stuck!" );
        }

        // Check if root has been found (within tolerance).
        solverStatus = gsl_multiroot_test_delta(
          solver->dx, solver->x, absoluteTolerance, relativeTolerance );
    } while ( solverStatus == GSL_CONTINUE && counter < maximumIterations );

    // Save number of iterations.
    numberOfIterations = counter - 1;

    // Print final status of solver to buffer.
    summary << std::endl;
    summary << "Status of non-linear solver: " << gsl_strerror( solverStatus ) << std::endl;
    summary << std::endl;

    // Write buffer contents to solver status summary string.
    solverStatusSummary = summary.str( );

    // Generate TLE with converged mean elements.
    Tle virtualTle = templateTle;

    Real convergedMeanEccentricity = gsl_vector_get( solver->x, 2 );
    if ( convergedMeanEccentricity < 0.0 )
    {
        convergedMeanEccentricity = std::fabs( gsl_vector_get( solver->x, 2 ) );
    }

    if ( convergedMeanEccentricity > 0.999 )
    {
        convergedMeanEccentricity = 0.99;
    }

    virtualTle.updateMeanElements( sml::computeModulo( std::fabs( gsl_vector_get( solver->x, 0 ) ), 180.0 ),
                                   sml::computeModulo( gsl_vector_get( solver->x, 1 ), 360.0 ),
                                   convergedMeanEccentricity,
                                   sml::computeModulo( gsl_vector_get( solver->x, 3 ), 360.0 ),
                                   sml::computeModulo( gsl_vector_get( solver->x, 4 ), 360.0 ),
                                   std::fabs( gsl_vector_get( solver->x, 5 ) ) );

    // Free up memory.
    gsl_multiroot_fsolver_free( solver );
    gsl_vector_free( initialGuessTleMeanElements );

    return virtualTle;
}

//! Convert Cartesian state to TLE (Two Line Elements).
template< typename Real, typename Vector6 >
const Tle convertCartesianStateToTwoLineElements( const Vector6& cartesianState,
                                                  const DateTime& epoch )
{
    std::string dummyString = "";
    int dummyint = 0;
    return convertCartesianStateToTwoLineElements< Real >(
        cartesianState, epoch, dummyString, dummyint );
}

//! Compute residuals for converting Cartesian state to TLE.
template< typename Real, typename Vector6 >
int computeCartesianToTwoLineElementResiduals( const gsl_vector* independentVariables,
                                               void* parameters,
                                               gsl_vector* residuals )
{
    const Vector6 targetState
        = static_cast< CartesianToTwoLineElementsParameters< Vector6 >* >( parameters )->targetState;
    const Tle templateTle
        = static_cast< CartesianToTwoLineElementsParameters< Vector6 >* >( parameters )->templateTle;

    // Create a TLE object with the mean TLE elements generated by the root-finder.
    Tle tle = templateTle;

    Real meanInclination = sml::computeModulo( std::fabs( gsl_vector_get( independentVariables, 0 ) ), 180.0 );
    Real meanRightAscendingNode = sml::computeModulo( gsl_vector_get( independentVariables, 1 ), 360.0 );

    Real meanEccentricity = gsl_vector_get( independentVariables, 2 );
    if ( meanEccentricity < 0.0 )
    {
        meanEccentricity = std::fabs( gsl_vector_get( independentVariables, 2 ) );
    }

    if ( meanEccentricity > 0.999 )
    {
        meanEccentricity = 0.99;
    }

    Real meanArgumentPerigee =  sml::computeModulo( gsl_vector_get( independentVariables, 3 ), 360.0 );
    Real meanMeanAnomaly =  sml::computeModulo( gsl_vector_get( independentVariables, 4 ), 360.0 );
    Real meanMeanMotion = std::fabs( gsl_vector_get( independentVariables, 5 ) );

    tle.updateMeanElements( meanInclination,
                            meanRightAscendingNode,
                            meanEccentricity,
                            meanArgumentPerigee,
                            meanMeanAnomaly,
                            meanMeanMotion );

    // Propagate the TLE object to the specified epoch using the SGP4 propagator.
    SGP4 sgp4( tle );
    Eci cartesianState = sgp4.FindPosition( 0.0 );

    // Compute residuals by computing the difference between the Cartesian state generated by the
    // SGP4 propagator and the target Cartesian state.
    gsl_vector_set( residuals, astro::xPositionIndex, cartesianState.Position( ).x - targetState[ astro::xPositionIndex ] );
    gsl_vector_set( residuals, astro::yPositionIndex, cartesianState.Position( ).y - targetState[ astro::yPositionIndex ] );
    gsl_vector_set( residuals, astro::zPositionIndex, cartesianState.Position( ).z - targetState[ astro::zPositionIndex ] );
    gsl_vector_set( residuals, astro::xVelocityIndex, cartesianState.Velocity( ).x - targetState[ astro::xVelocityIndex ] );
    gsl_vector_set( residuals, astro::yVelocityIndex, cartesianState.Velocity( ).y - targetState[ astro::yVelocityIndex ] );
    gsl_vector_set( residuals, astro::zVelocityIndex, cartesianState.Velocity( ).z - targetState[ astro::zVelocityIndex ] );

    return GSL_SUCCESS;
}

//! Compute initial guess for TLE mean elements.
template< typename Real, typename Vector6 >
const Vector6 computeInitialGuessTleMeanElements( const Vector6& keplerianElements,
                                                  const Real earthGravitationalParameter )

{
    Vector6 tleMeanElements = keplerianElements;

    // Compute mean inclination [deg].
    tleMeanElements[ 0 ]
    = sml::computeModulo(
        sml::convertRadiansToDegrees( keplerianElements[ astro::inclinationIndex ] ), 180.0 );

    // Compute mean right ascending node [deg].
    tleMeanElements[ 1 ]
        = sml::computeModulo(
            sml::convertRadiansToDegrees(
                keplerianElements[ astro::longitudeOfAscendingNodeIndex ] ), 360.0 );

    // Compute mean eccentricity [-].
    tleMeanElements[ 2 ] = keplerianElements[ astro::eccentricityIndex ];

    // Compute mean argument of perigee [deg].
    tleMeanElements[ 3 ]
        = sml::computeModulo(
            sml::convertRadiansToDegrees(
                keplerianElements[ astro::argumentOfPeriapsisIndex ] ), 360.0 );

    // Compute mean eccentric anomaly [rad].
    const Real eccentricAnomaly
        = astro::convertTrueAnomalyToEccentricAnomaly(
            keplerianElements[ astro::trueAnomalyIndex ],
            keplerianElements[ astro::eccentricityIndex ] );

    // Compute mean mean anomaly [deg].
    tleMeanElements[ 4 ]
        = sml::computeModulo(
            sml::convertRadiansToDegrees(
                astro::convertEccentricAnomalyToMeanAnomaly(
                    eccentricAnomaly, keplerianElements[ astro::eccentricityIndex ] ) ), 360.0 );

    // Compute new mean motion [rev/day].
    tleMeanElements[ 5 ]
        = astro::computeKeplerMeanMotion(
            keplerianElements[ astro::semiMajorAxisIndex ],
            earthGravitationalParameter ) / ( 2.0 * sml::SML_PI ) * astro::ASTRO_JULIAN_DAY_IN_SECONDS;

    return tleMeanElements;
}

//! Parameter struct used by Cartesian-to-TLE residual function.
template< typename Vector6 >
struct CartesianToTwoLineElementsParameters
{
public:

    //! Constructor taking parameter values.
    /*!
     * Default constructor, taking parameters for Cartesian-to-Two-Line-Elements conversion.
     * @sa convertCartesianStateToTwoLineElements, computeCartesianToTwoLineElementResiduals
     *
     * @tparam Vector6                      Vector of length 6
     * @param  aTargetState                 Target Cartesian state [km; km/s]
     * @param  aTemplateTle                 Template for Two-Line-Elements (TLE) with pre-filled
     *                                      values
     */
    CartesianToTwoLineElementsParameters(
        const Vector6& aTargetState,
        const Tle& aTemplateTle )
        : targetState( aTargetState ),
          templateTle( aTemplateTle )
    { }

    //! Target state in Cartesian elements [km; km/s].
    const Vector6 targetState;

    //! Template TLE that contains pre-filled values (e.g., epoch, Bstar).
    const Tle templateTle;

protected:

private:
};

} // namespace atom
Example #4
0
namespace atom
{

//! Execute Atom solver.
/*!
 * Executes Atom solver to find the transfer orbit connecting two positions. The epoch of the
 * departure position and the Time-of-flight need to be specified.
 *
 * The Atom solver is an analog of the Lambert solver (Lancaster and Blanchard, 1969;
 * Gooding, 1990; Izzo, 2014), that aims to find the conic section that bridges two positions, at
 * given epochs, by using impulsive manveuvers (Delta-V maneuvers) at departure and arrival. The
 * Atom solver aims to solver a similar orbital transfer, subject to perturbations. The
 * perturbations taken into account are those encoded in the SGP4/SDP4 propagators (Vallado, 2006).
 *
 * Since the Atom solver makes use fo the SGP4/SDP4 propagators, it can currently only solve for
 * perturbed transfers around the Earth. As a result, the Earth's gravitational parameter is fixed,
 * as specified by the SGP4/SDP4 propagators (Vallado, 2006).
 *
 * Details of the underlying non-linear system and algorithm are catalogued by
 * Kumar, et al. (2014).
 *
 * @sa     convertCartesianStateToTwoLineElements
 * @tparam Real                        Type for reals
 * @tparam Vector3                     Type for 3-vector of reals
 * @param  departurePosition           Cartesian position vector at departure [km]
 * @param  departureEpoch              Modified Julian Date (MJD) of departure
 * @param  arrivalPosition             Cartesian position vector at arrival [km]
 * @param  timeOfFlight                Time-of-Flight for orbital transfer [s]
 * @param  departureVelocityGuess      Initial guess for the departure velocity (serves as initial
 *                                     guess for the internal root-finding procedure) [km/s]
 * @param  solverStatusSummary         Status of non-linear solver printed as a table
 * @param  numberOfIterations          Number of iterations completed by solver
 * @param  referenceTle                Reference Two Line Elements [default: 0-TLE]
 * @param  earthGravitationalParameter Earth gravitational parameter [km^3 s^-2] [default: mu_SGP]
 * @param  earthMeanRadius             Earth mean radius [km] [default: R_SGP]
 * @param  absoluteTolerance           Absolute tolerance used to check if root-finder has
 *                                     converged [default: 1.0e-10] (see Kumar, et al. (2014) for
 *                                     details on how convergence is tested)
 * @param  relativeTolerance           Relative tolerance used to check if root-finder has
 *                                     converged [default: 1.0e-5] (see Kumar, et al. (2014) for
 *                                     details on how convergence is tested)
 * @param  maximumIterations           Maximum number of solver iterations permitted. Once the
 *                                     solver reaches this limit, the loop will be broken and the
 *                                     solver status will report that it has not converged
 *                                     [default: 100].
 * @return                             Departure and arrival velocities (stored in that order)
 */
template< typename Real, typename Vector3 >
const std::pair< Vector3, Vector3 > executeAtomSolver(
    const Vector3& departurePosition,
    const DateTime& departureEpoch,
    const Vector3& arrivalPosition,
    const Real timeOfFlight,
    const Vector3& departureVelocityGuess,
    std::string& solverStatusSummary,
    int& numberOfIterations,
    const Tle& referenceTle = Tle( ),
    const Real earthGravitationalParameter = kMU,
    const Real earthMeanRadius = kXKMPER,
    const Real absoluteTolerance = 1.0e-10,
    const Real relativeTolerance = 1.0e-5,
    const int maximumIterations = 100 );

//! Execute Atom solver.
/*!
 * Executes Atom solver to find the transfer orbit connecting two positions. The epoch of the
 * departure position and the Time-of-flight need to be specified.
 *
 * The Atom solver is an analog of the Lambert solver (Lancaster and Blanchard, 1969;
 * Gooding, 1990; Izzo, 2014), that aims to find the conic section that bridges two positions, at
 * given epochs, by using impulsive manveuvers (Delta-V maneuvers) at departure and arrival. The
 * Atom solver aims to solver a similar orbital transfer, subject to perturbations. The
 * perturbations taken into account are those encoded in the SGP4/SDP4 propagators (Vallado, 2006).
 *
 * Since the Atom solver makes use fo the SGP4/SDP4 propagators, it can currently only solve for
 * perturbed transfers around the Earth. As a result, the Earth's gravitational parameter is fixed,
 * as specified by the SGP4/SDP4 propagators (Vallado, 2006).
 *
 * Details of the underlying non-linear system and algorithm are catalogued by
 * Kumar, et al. (2014).
 *
 * This is a function overload to ensure that the user can opt to leave out solver summary status
 * string and number of iterations counter from the call (overload is necessary since non-const
 * references cannot be assigned default values in C++).
 *
 * @sa     convertCartesianStateToTwoLineElements
 * @tparam Real                   Type for reals
 * @tparam Vector3                Type for 3-vector of reals
 * @param  departurePosition      Cartesian position vector at departure [km]
 * @param  departureEpoch         Modified Julian Date (MJD) of departure
 * @param  arrivalPosition        Cartesian position vector at arrival [km]
 * @param  timeOfFlight           Time-of-Flight for orbital transfer [s]
 * @param  departureVelocityGuess Initial guess for the departure velocity (serves as initial
 *                                guess for the internal root-finding procedure) [km/s]
 * @return                        Departure and arrival velocities (stored in that order)
 */
template< typename Real, typename Vector3 >
const std::pair< Vector3, Vector3 > executeAtomSolver(
    const Vector3& departurePosition,
    const DateTime& departureEpoch,
    const Vector3& arrivalPosition,
    const Real timeOfFlight,
    const Vector3& departureVelocityGuess );

//! Compute residuals to execute Atom solver.
/*!
 * Evaluates system of non-linear equations and computes residuals to execute the Atom solver. The
 * residual function, \f$\bar{R}\f$ is computed as follows:
 * The system of non-linear equations used is:
 *  \f[
 *      \bar{R} = 0 = \frac{\bar{r}_{new} - \bar{r}_{target}}{R_{Earth}}
 *  \f]
 * where \f$\bar{r}_{new}\f$ is the Cartesian position computed by propagating the
 * initial, prescribed state under the action of an initial impulsive Delta V, by a prescribed
 * time-of-flight, \f$\bar{r}_{target}\f$ is the target Cartesian position and
 * \f$R_{Earth}\f$ is the mean radius of the Earth. Note that the residuals are non-dimensional.
 * They are used to drive a root-finding process that uses the GSL library.
 *
 * @sa executeAtomSolver
 * @tparam Real                 Type for reals
 * @tparam Vector3              Type for 3-vector of reals
 * @param  independentVariables Vector of independent variables used by the root-finder
 * @param  parameters           Parameters required to compute the objective function
 * @param  residuals            Vector of computed residuals
 * @return                      GSL flag indicating success or failure
 */
template< typename Real, typename Vector3 >
int computeAtomResiduals( const gsl_vector* independentVariables,
                          void* parameters,
                          gsl_vector* residuals );

//! Parameter struct used by Atom residual function.
/*!
 * Data structure with parameters used to compute Atom residual function.
 *
 * @sa computeAtomResiduals
 * @tparam Real    Type for reals
 * @tparam Vector3 Type for 3-vector of reals
 */
template< typename Real, typename Vector3 >
struct AtomParameters;

//! Execute Atom solver.
template< typename Real, typename Vector3 >
const std::pair< Vector3, Vector3 > executeAtomSolver(
    const Vector3& departurePosition,
    const DateTime& departureEpoch,
    const Vector3& arrivalPosition,
    const Real timeOfFlight,
    const Vector3& departureVelocityGuess,
    std::string& solverStatusSummary,
    int& numberOfIterations,
    const Tle& referenceTle,
    const Real earthGravitationalParameter,
    const Real earthMeanRadius,
    const Real absoluteTolerance,
    const Real relativeTolerance,
    const int maximumIterations )
{
    // Set up parameters for residual function.
    AtomParameters< Real, Vector3 > parameters( departurePosition,
                                                departureEpoch,
                                                arrivalPosition,
                                                timeOfFlight,
                                                earthGravitationalParameter,
                                                earthMeanRadius,
                                                referenceTle,
                                                absoluteTolerance,
                                                relativeTolerance,
                                                maximumIterations );

    // Set up residual function.
    gsl_multiroot_function atomFunction
        = {
            &computeAtomResiduals< Real, Vector3 >, 3, &parameters
          };

    // Set initial guess.
    gsl_vector* initialGuess = gsl_vector_alloc( 3 );
    for ( int i = 0; i < 3; i++ )
    {
        gsl_vector_set( initialGuess, i, departureVelocityGuess[ i ] );
    }

    // Set up solver type (derivative free).
    const gsl_multiroot_fsolver_type* solverType = gsl_multiroot_fsolver_hybrids;

    // Allocate memory for solver.
    gsl_multiroot_fsolver* solver = gsl_multiroot_fsolver_alloc( solverType, 3 );

    // Set solver to use residual function with initial guess.
    gsl_multiroot_fsolver_set( solver, &atomFunction, initialGuess );

     // Declare current solver status and iteration counter.
    int solverStatus = false;
    int counter = 0;

    // Set up buffer to store solver status summary table.
    std::ostringstream summary;

    // Print header for summary table to buffer.
    summary << printAtomSolverStateTableHeader( );

    do
    {
        // Print current state of solver for summary table.
        summary << printAtomSolverState( counter, solver );

        // Increment iteration counter.
        ++counter;
        // Execute solver iteration.
        solverStatus = gsl_multiroot_fsolver_iterate( solver );

        // Check if solver is stuck; if it is stuck, break from loop.
        if ( solverStatus )
        {
            std::cerr << "GSL solver status: " << solverStatus << std::endl;
            std::cerr << summary.str( ) << std::endl;
            std::cerr << std::endl;
            throw std::runtime_error( "ERROR: Non-linear solver is stuck!" );
        }

        // Check if root has been found (within tolerance).
        solverStatus = gsl_multiroot_test_delta(
          solver->dx, solver->x, absoluteTolerance, relativeTolerance );
    } while ( solverStatus == GSL_CONTINUE && counter < maximumIterations );

    // Save number of iterations.
    numberOfIterations = counter - 1;

    // Print final status of solver to buffer.
    summary << std::endl;
    summary << "Status of non-linear solver: " << gsl_strerror( solverStatus ) << std::endl;
    summary << std::endl;

    // Write buffer contents to solver status summary string.
    solverStatusSummary = summary.str( );

    // Store final departure velocity.
    Vector3 departureVelocity = departureVelocityGuess;
    for ( int i = 0; i < 3; i++ )
    {
        departureVelocity[ i ] = gsl_vector_get( solver->x, i );
    }

    // Set departure state [km/s].
    std::vector< Real > departureState( 6 );
    for ( int i = 0; i < 3; i++ )
    {
        departureState[ i ] = departurePosition[ i ];
    }
    for ( int i = 0; i < 3; i++ )
    {
        departureState[ i + 3 ] = departureVelocity[ i ];
    }

    // Convert departure state to TLE.
    std::string dummyString = "";
    int dummyint = 0;
    const Tle departureTle = convertCartesianStateToTwoLineElements< Real >(
        departureState,
        departureEpoch,
        dummyString,
        dummyint,
        referenceTle,
        earthGravitationalParameter,
        earthMeanRadius,
        absoluteTolerance,
        relativeTolerance,
        maximumIterations );

    // Propagate departure TLE by time-of-flight using SGP4 propagator.
    SGP4 sgp4( departureTle );
    DateTime arrivalEpoch = departureEpoch.AddSeconds( timeOfFlight );
    Eci arrivalState = sgp4.FindPosition( arrivalEpoch );

    Vector3 arrivalVelocity = departureVelocity;
    arrivalVelocity[ 0 ] = arrivalState.Velocity( ).x;
    arrivalVelocity[ 1 ] = arrivalState.Velocity( ).y;
    arrivalVelocity[ 2 ] = arrivalState.Velocity( ).z;

    // Free up memory.
    gsl_multiroot_fsolver_free( solver );
    gsl_vector_free( initialGuess );

    // Return departure and arrival velocities.
    return std::make_pair< Vector3, Vector3 >( departureVelocity, arrivalVelocity );
}

//! Execute Atom solver.
template< typename Real, typename Vector3 >
const std::pair< Vector3, Vector3 > executeAtomSolver(
    const Vector3& departurePosition,
    const DateTime& departureEpoch,
    const Vector3& arrivalPosition,
    const Real timeOfFlight,
    const Vector3& departureVelocityGuess )
{
    std::string dummyString = "";
    int dummyint = 0;
    return executeAtomSolver( departurePosition,
                              departureEpoch,
                              arrivalPosition,
                              timeOfFlight,
                              departureVelocityGuess,
                              dummyString,
                              dummyint );
}

//! Compute residuals to execute Atom solver.
template< typename Real, typename Vector3 >
int computeAtomResiduals( const gsl_vector* independentVariables,
                          void* parameters,
                          gsl_vector* residuals )
{
    // Store parameters locally.
    const Vector3 departurePosition = static_cast< AtomParameters< Real, Vector3 >* >(
        parameters )->departurePosition;

    const DateTime departureEpoch
        = static_cast< AtomParameters< Real, Vector3 >* >( parameters )->departureEpoch;

    const Vector3 targetPosition = static_cast< AtomParameters< Real, Vector3 >* >(
        parameters )->targetPosition;

    const Real timeOfFlight
        = static_cast< AtomParameters< Real, Vector3 >* >(
            parameters )->timeOfFlight;

    const Real earthGravitationalParameter
        = static_cast< AtomParameters< Real, Vector3 >* >(
            parameters )->earthGravitationalParameter;

    const Real earthMeanRadius
        = static_cast< AtomParameters< Real, Vector3 >* >( parameters )->earthMeanRadius;

    const Tle referenceTle
        = static_cast< AtomParameters< Real, Vector3 >* >( parameters )->referenceTle;

    const Real absoluteTolerance
        = static_cast< AtomParameters< Real, Vector3 >* >( parameters )->absoluteTolerance;

    const Real relativeTolerance
        = static_cast< AtomParameters< Real, Vector3 >* >( parameters )->relativeTolerance;

    const int maximumIterations
        = static_cast< AtomParameters< Real, Vector3 >* >( parameters )->maximumIterations;

    // Set Departure state [km; km/s].
    std::vector< Real > departureVelocity( 3 );
    for ( int i = 0; i < 3; i++ )
    {
        departureVelocity[ i ] = gsl_vector_get( independentVariables, i );
    }

    std::vector< Real > departureState( 6 );
    for ( int i = 0; i < 3; i++ )
    {
        departureState[ i ] = departurePosition[ i ];
    }
    for ( int i = 0; i < 3; i++ )
    {
        departureState[ i + 3 ] = departureVelocity[ i ];
    }

    // Convert departure state to TLE.
    std::string dummyString = "";
    int dummyint = 0;
    const Tle departureTle = convertCartesianStateToTwoLineElements(
        departureState,
        departureEpoch,
        dummyString,
        dummyint,
        referenceTle,
        earthGravitationalParameter,
        earthMeanRadius,
        absoluteTolerance,
        relativeTolerance,
        maximumIterations );

    // Propagate departure TLE by time-of-flight using SGP4 propagator.
    SGP4 sgp4( departureTle );
    DateTime arrivalEpoch = departureEpoch.AddSeconds( timeOfFlight );
    Eci arrivalState = sgp4.FindPosition( arrivalEpoch );

    // Evaluate system of non-linear equations and store residuals.
    gsl_vector_set( residuals, 0,
                    ( arrivalState.Position( ).x - targetPosition[ 0 ] ) / earthMeanRadius );
    gsl_vector_set( residuals, 1,
                    ( arrivalState.Position( ).y - targetPosition[ 1 ] ) / earthMeanRadius );
    gsl_vector_set( residuals, 2,
                    ( arrivalState.Position( ).z - targetPosition[ 2 ] ) / earthMeanRadius );

    return GSL_SUCCESS;
}

//! Parameter struct used by Atom residual function.
template< typename Real, typename Vector3 >
struct AtomParameters
{
public:

    //! Constructor taking parameter values.
    /*!
     * Default constructor, taking parameters to execute Atom solver.
     * @sa executeAtomSolver, computeCartesianToTwoLineElementResiduals
     * @param aDeparturePosition            Cartesian departure position [km]
     * @param aDepartureEpoch               Modified Julian Date (MJD) of departure
     * @param aTargetPosition               Target Cartesian position [km]
     * @param aTimeOfFlight                 Time-of-Flight (TOF) [s]
     * @param anEarthGravitationalParameter Earth gravitational parameter [km^3 s^-2]
     * @param anEarthMeanRadius             Earth mean radius [km]
     * @param aReferenceTle                 Reference Two-Line-Elements
     * @param anAbsoluteTolerance           Absolute tolerance used to check for convergence
     * @param aRelativeTolerance            Relative tolerance used to check for convergence
     * @param someMaximumIterations         Maximum number of solver iterations permitted
     */
    AtomParameters(
        const Vector3& aDeparturePosition,
        const DateTime& aDepartureEpoch,
        const Vector3& aTargetPosition,
        const Real aTimeOfFlight,
        const Real anEarthGravitationalParameter,
        const Real anEarthMeanRadius,
        const Tle& aReferenceTle,
        const Real anAbsoluteTolerance,
        const Real aRelativeTolerance,
        const int someMaximumIterations )
        : departurePosition( aDeparturePosition ),
          departureEpoch( aDepartureEpoch ),
          targetPosition( aTargetPosition ),
          timeOfFlight( aTimeOfFlight ),
          earthGravitationalParameter( anEarthGravitationalParameter ),
          earthMeanRadius( anEarthMeanRadius ),
          referenceTle( aReferenceTle ),
          absoluteTolerance( anAbsoluteTolerance ),
          relativeTolerance( aRelativeTolerance ),
          maximumIterations( someMaximumIterations )
    { }

    //! Departure position in Cartesian elements [km].
    const Vector3 departurePosition;

    //! Departure epoch in Modified Julian Date (MJD).
    const DateTime departureEpoch;

    //! Target position in Cartesian elements [km].
    const Vector3 targetPosition;

    //! Time-of-Flight (TOF) [s].
    const Real timeOfFlight;

    //! Earth gravitational parameter [km^3 s^-2].
    const Real earthGravitationalParameter;

    //! Earth mean radius [km].
    const Real earthMeanRadius;

    //! Reference TLE.
    const Tle referenceTle;

    //! Absolute tolerance [-].
    const Real absoluteTolerance;

    //! Relative tolerance [-].
    const Real relativeTolerance;

    //! Maximum number of iterations.
    const int maximumIterations;

protected:

private:
};

} // namespace atom