2.4. Aerodynamic Guidance

When including aerodynamics in the orbit propagation, it may often be desirable to modify the aerodynamic properties of the vehicle as a function of the current environment. This is the case for ascent and entry for instance. Also, control surface deflections may be needed to modify the vehicle’s behaviour. Both options are included in Tudat, and the interfaces are described below.

Note

In applications were no aerodynamic guidance is used, the angle of attack, sideslip angle and bank angle are all implicitly set to zero.

2.4.1. Vehicle orientation

Although rotational motion can be propagated in Tudat (see RotationalStatePropagatorSettings), sometime the user simply wants to impose this rotational motion on the vehicle.

For aerodynamics, the vehicle orientation is typically described by the angle of attack, angle of sideslip and bank angle, which is also the approach we take in Tudat. There is a single interface in Tudat through which the functions describing these angles are fed to the trajectory propagation:

class AerodynamicAngleCalculator

This class is used for the describing the angle of attack, angle of sideslip and bank angle of the vehicle. The setOrientationAngleFunctions member function of this class is used. However, there is a number of manners in which to use this function, either directly or indirectly, the preferred selection of course depending on your application. The options are:

  • Manual definitions of the three angles of a function of time (can also be used to set constant angles).
  • Pre-defined guidance laws (only angle-of-attack trim presently implemented).
  • A user-defined AerodynamicGuidance class, which computes the current angles using any number of input/environment variables. This option is typically the preferred option for a realistic entry propagation.

Below, we will indicate how to use these three options for the example of the Apollo entry example. Currently, the following code is used in the example, defining a constant angle of attack of 30 degree, and 0 sideslip and bank angle:

bodyMap.at( "Apollo" )->getFlightConditions( )->getAerodynamicAngleCalculator( )->setOrientationAngleFunctions(
            [ = ]( ){ return 30.0 * mathematical_constants::PI / 180.0; } );

Warning

We stress that any definition of the aerodynamic guidance must be done after the acceleration models have been created (by AccelerationSettings)(but before the trajectory is propagated).

2.4.1.1. Manual angle functions

To manually set the body orientation as constant angles, the setOrientationAngleFunctions function is called directly, as is done in the above example. Note that if you need a definition of aerodynamic angles that varies with time, a specific AerodynamicGuidance derived class should be implemented or one of the predefined orientation functions should be used. In general, the manual definition of the angles is done as follows:

double constantAngleOfAttack = 30.0 * mathematical_constants::PI / 180.0;
double constantSideslipAngle = 2.0 * mathematical_constants::PI / 180.0;
double constantBankAngle = -70.0 * mathematical_constants::PI / 180.0;

bodyMap.at( "Apollo" )->getFlightConditions( )->getAerodynamicAngleCalculator( )->setOrientationAngleFunctions(
            [ = ]( ){ return constantAngleOfAttack; }, [ = ]( ){ return constantSideslipAngle; }, [ = ]( ){ return constantBankAngle; } );

By using this code, the vehicle will fly at 30 degree angle of attack, 2 degree sideslip angle and -70 degree bank angle during the full propagation.

2.4.1.2. Predefined guidance law

Presently, only a single predefined guidance law is implemented in Tudat: imposing pitch-trim. When using this setting, the sideslip and bank angles are set to zero, while the angle of attack is chosen such that the pitch moment coefficient is exactly zero. It is set by using:

std::shared_ptr< aerodynamics::TrimOrientationCalculator > trimCalculator = setTrimmedConditions( bodyMap.at( "Apollo" ) );

After calling this function, no additional action is needed from the user. In fact, using the following:

setTrimmedConditions( bodyMap.at( "Apollo" ) );

will work equally well. The TrimOrientationCalculator is returned by the function to keep the object through which the computations are performed available to the user.

2.4.1.3. User-defined aerodynamic orientation

For a general description of the vehicle orientation, a custom-defined function is typically required, to fit the needs to the mission/simulation under consideration. To facilitate this process, we have defined a virtual base class called AerodynamicGuidance.

class AerodynamicGuidance

Virtual base class used to facilitate user-defined derived guidance classes.

A user-defined derived class must be defined, through which the orientation is computed at each time step of the propagation. Below, there are several examples of how to implement such a guidance algorithm. In each case, the final binding to the propagation is done as follows:

std::shared_ptr< aerodynamics::AerodynamicGuidance > aerodynamicGuidance =  // Create user-defined guidance object here
setGuidanceAnglesFunctions( aerodynamicGuidance, bodyMap.at( "Apollo" ) );

An example of the computation of the three aerodynamic angles as a function of time alone can be done by using the following AerodynamicGuidance derived class:

class LinearTimeAerodynamicGuidance: public AerodynamicGuidance
{
    LinearTimeAerodynamicGuidance(
        const double angleOfAttackRate, const double sideslipAngleRate, const double bankAngleRate,
            const double referenceTime ):
                angleOfAttackRate_( angleOfAttackRate ), sideslipAngleRate_( sideslipAngleRate ), bankAngleRate_( bankAngleRate ),
                    referenceTime_( referenceTime ){ }

void updateGuidance( const double currentTime )
{
    currentAngleOfAttack_ = angleOfAttackRate_ * ( currentTime - referenceTime_ );
    currentAngleOfSideslip_ = sideslipAngleRate_ * ( currentTime - referenceTime_ );
    currentBankAngle_ = bankAngleRate_ * ( currentTime - referenceTime_ );
}

private:

    double angleOfAttackRate_;

    double sideslipAngleRate_;

    double bankAngleRate_;

    double referenceTime_;
};

Then, the guidance law can be created and set by:

std::shared_ptr< aerodynamics::AerodynamicGuidance > aerodynamicGuidance = std::make_shared< LinearTimeAerodynamicGuidance >(
    1.0E-4, -2.0E-6, 1.0E-3, 500.0 );
setGuidanceAnglesFunctions( aerodynamicGuidance, bodyMap.at( "Apollo" ) );

This creates and sets aerodynamic angles that are zero at t=500 s, where the angles of attack, sideslip and bank change by 10 -4, -2*10 -6 and 10 -3 rad/s. Recall that al units in Tudat are SI unless otherwise indicated. The key behind this implementation in the AerodynamicGuidance derived class is the following:

  • A definition of a void updateGuidance( const double currentTime ) function in the derived class, which is called every time step to compute the current angles as a function of time.
  • The calculation of currentAngleOfAttack_currentAngleOfSideslip_ and currentBankAngle_ in this function. Whichever values these variables are set to in the updateGuidance function are the values that will be used during the current time step.

The example of aerodynamic guidance given above is not very representative, of course. In general, you will want to define your body’s orientation as a function of its current state/environment, etc. To accomplish this, you can add the body map (or any its contents) as member variables to your AerodynamicGuidance derived class. In many cases, the required information will be stored in the FlightConditions object.

class FlightConditions

Class which calculates and stores data on altitude, longitude, latitude, etc. for non-atmospheric flight (stored as a member of a Body object). Can be used during propagation to retrieve various data, using the functions:

  • getCurrentAltitude Returns the current altitude w.r.t. the central body shape model
  • getCurrentLongitude Returns the current longitude, in the body-fixed frame of the central body
  • getCurrentGeodeticLatitude Returns the current geodetic latitude, in the body-fixed frame of the central body
  • getAerodynamicAngleCalculator Returns a AerodynamicAngleCalculator object, from which the current geographic latitude, longitude, flight path angle, heading angle, bank angle, sideslip angle and angle of attack can be retrieved.
class AtmosphericFlightConditions

Class, derived from FlightConditions, which stores data on altitude, longitude, latitude, density, airspeed, etc. for atmospheric flight. Can be used during propagation to retrieve various data. It derives all functions of FlightConditions, and also provides the functions:

  • getCurrentDensity Returns the current atmospheric density at the vehicle current state.
  • getCurrentFreestreamTemperature Returns the current atmospheric temperature at the vehicle current state.
  • getCurrentDynamicPressure Returns the current dynamic pressure at the vehicle current state.
  • getCurrentPressure Returns the current static pressure at the vehicle current state.
  • getCurrentAirspeed Returns the current airspeed at the vehicle current state.
  • getCurrentSpeedOfSound Returns the current speed of sound at the vehicle current state.
  • getCurrentMachNumber Returns the current Mach number at the vehicle current state.
  • getCurrentAirspeedBasedVelocity Returns the current velocity vectorof the vehicle w.r.t. the atmosphere, expressed in the frame fixed to the central body.
  • getAerodynamicCoefficientInterface Returns the object of type AerodynamicCoefficientInterface responsible for computing and updating the aerodynamic coefficients.
  • getAerodynamicCoefficientIndependentVariables Returns the current list of independent variables of teh aerodynamic coefficients. For instance, if the coefficients depend on Mach number, angle of attack and sideslip angle, this function returns a vector containing these three variables (in order).

An example of an implementation of an aerodynamic guidance class is given and discussed below.

class FlightConditionsBasedAerodynamicGuidance: public AerodynamicGuidance
{
    FlightConditionsBasedAerodynamicGuidance(
            const NamedBodyMap& bodyMap,
            const std::string vehicleName )
    {
        vehicleFlightConditions_ =
           std::dynamic_pointer_cast< AtmosphericFlightConditions >(
              bodyMap.at( vehicleName )->getFlightConditions( ) );
        if( vehicleFlightConditions_ == nullptr )
        {
            throw std::runtime_error( "Error in FlightConditionsBasedAerodynamicGuidance, expected AtmosphericFlightConditions" );
        }
    }

    void updateGuidance( const double currentTime );

private:

    std::shared_ptr< AtmosphericFlightConditions > vehicleFlightConditions_;
};

where the updateGuidance function is not defined directly in the .h file, but instead in the .cpp file (below). Note the dynamic_pointer_cast in the constructor, and the check to see if the FlightConditions object is actually of the type AtmosphericFlightConditions.

As an example for a guidance scheme, let’s consider the simplified (and still not particularly realistic) aerodynamic guidance where:

  • Angle of attack is 35 degrees is altitude is larger than 60 km, angle of attack is 5 degrees at 30 km, and changes linearly between these two values.
  • Sideslip angle is always zero.
  • Bank angle is 80 degrees if mach number is larger than 8.

The implementation of the updateGuidance functions in the .cpp file would then read:

void FlightConditionsBasedAerodynamicGuidance::updateGuidance( const double currentTime )
{
    if( vehicleFlightConditions_->getCurrentAltitude( ) > 60.0E3 )
    {
        currentAngleOfAttack_ = 35.0 * mathematical_constants::PI / 180.0;
    }
    else if( vehicleFlightConditions_->getCurrentAltitude( ) < 25.0E3 )
    {
        currentAngleOfAttack_ = 5.0 * mathematical_constants::PI / 180.0;
    }
    else
    {
        currentAngleOfAttack_ = ( 5.0 + 30.0 * ( vehicleFlightConditions_->getCurrentAltitude( ) - 25.0E3 ) / 35.0E3 ) * mathematical_constants::PI / 180.0;

    }

    currentAngleOfSideslip_ = 0.0;

    if( vehicleFlightConditions_->getCurrentMachNumber( ) < 8 )
    {
        currentBankAngle_ = 80.0 * mathematical_constants::PI / 180.0;
    }
    else
    {
        currentBankAngle_ = 0.0;
    }
}

Although this guidance profile is still not very realistic for full numerical simulations, it does show the manner in which the interface is to be set up for a more realistic approach.

2.4.1.4. Using the environment models

In computing your aerodynamic guidance commands, you will likely need to use a number of physical quantities from your environment, as is the case with the example above, where the altitude is used. Below, a list is given with the way in which to retrieve some variables that are typical in aerodynamic guidance:

  • Current conditions at a vehicle’s location w.r.t. a central central body: These are stored in an object of type FlightConditions, which may be of the AtmosphericFlightConditions derived class, as in the case above (stored in a Body object; retrieved by using the getFlightConditions function). In the FlightConditions and AtmosphericFlightConditions class code, you will see a number of functions called getCurrent.... These functions are key in linking the environment with the guidance. When called from the AerodynamicGuidance derived class, the current value of the associated quantity is returned (e.g. getCurrentAltitude returns altitude, getCurrentAirspeed returns airspeed, etc.).
  • Aerodynamic coefficients: These often play a particularly important role in the aerodynamic guidance. Whereas the other dependent variables are computed before updating the angles of attack, sideslip and bank, the aerodynamic coefficients are computed as a function of these angles. Therefore, the ‘current aerodynamic coefficients’ cannot yet be retrieved from the environment when updating the guidance. However, if the angles on which the aerodynamic coefficients depend have already been locally computed (in currentAngleOfAttack_, etc.), they may be used for determination of subsequent angles. Below is an example of aerodynamic coefficients depending on angle of attack, angle of sideslip and Mach number and the bank angle determined as a function of aerodynamic coefficients. The following can then be used inside the updateGuidance function:
// Define aerodynamic coefficient interface/flight conditions (typically retrieved from body map; may also be a member variable)
std::shared_ptr< aerodynamics::AerodynamicCoefficientInterface > coefficientInterface_ = ...
std::shared_ptr< aerodynamics::AtmosphericFlightConditions > flightConditions_ = ...

// Compute angles of attack and sideslip
currentAngleOfAttack_ = ...
currentAngleOfSideslip_ = ...

// Define input to aerodynamic coefficients: take care of order of input (this depends on how the coefficients are created)!
std::vector< double > currentAerodynamicCoefficientsInput_;
currentAerodynamicCoefficientsInput_.push_back( currentAngleOfAttack_ );
currentAerodynamicCoefficientsInput_.push_back( currentAngleOfSideslip_ );
currentAerodynamicCoefficientsInput_.push_back( flightConditions_->getCurrentMachNumber( ) );

// Update and retrieve current aerodynamic coefficients
coefficientInterface_->updateCurrentCoefficients( currentAerodynamicCoefficientsInput_ );
Eigen::Vector3d currentAerodynamicCoefficients = coefficientInterface_->getCurrentForceCoefficients( );

// Compute bank angle
currentBankAngle_ =  some function of currentAerodynamicCoefficients

Note that the physical meaning of the coefficients may differ, depending on how they are defined in AerodynamicCoefficientSettings: if they are defined in the aerodynamic frame (C_D, C_S, C_L) this is how they are returned.

  • Current vehicle orientation angles: In particular, the angles used to define the spherical vehicle state: latitude, longitude, flight path angle and heading angle may be needed. These are retrieved from an object of type AerodynamicAngleCalculator, which is retrieved from the FlightConditions class with the getAerodynamicAngleCalculator function. The AerodynamicAngleCalculator class in turn has a function getAerodynamicAngle, which takes a single argument: the type of angle that is to be returned. You can use any of the first four identifiers in the AerodynamicsReferenceFrameAngles. In the aerodynamic guidance, DO NOT use this function to retrieve the angle of attack, sideslip or bank. As an example, you can use:
// Define aerodynamic coefficient interface/flight conditions (typically retrieved from body map; may also be a member variable)
std::shared_ptr< aerodynamics::FlightConditions > flightConditions_ = ...
double currentFlightPathAngle = flightConditions_->getAerodynamicAngleCalculator( )->getAerodynamicAngle( reference_frames::flight_path_angle );
  • Body mass: The mass of the body at the current time is retrieved directly from the Body object using the getBodyMass( ) function.

2.4.2. Control surface deflections

For a realistic vehicle entry/ascent trajectory propagation, it will often be necessary to include control surface deflections in the numerical propagation. How to load/define the aerodynamic influence of control surfaces is discussed at the end of this page.

To use the control surface increments, the control surface deflections have to be set, either to a constant value before stating the propagation, or every time step by a user-defined guidance system. In each case, the control surface deflections are stored in a VehicleSystems object, which is a member of a Body object. The vehicle systems represent a collection of all physical (hardware) properties of a vehicle including the control surface deflections and properties. Presently, the only quantities that are stored for the control surfaces are the current deflection.

Tip

If your application requires more extensive functionality, please open an issue requesting this feature on Github).

In either case, a VehicleSystems object must be created and stored in the associated Body object:

std::shared_ptr< system_models::VehicleSystems > systemsModels = std::make_shared< system_models::VehicleSystems >( );
bodyMap[ "Vehicle" ]->setVehicleSystems( systemsModels );

The control surface deflections are then set by:

double elevonDeflection = 0.1;
std::string controlSurfaceId = "Elevon";
apolloSystems->setCurrentControlSurfaceDeflection( controlSurfaceId, elevonDeflection );

Note that the deflections of multiple control surfaces can be set in exactly the same manner as follows:

apolloSystems->setCurrentControlSurfaceDeflection( "Elevon", 0.1 );
apolloSystems->setCurrentControlSurfaceDeflection( "Aileron1", -0.15 );
apolloSystems->setCurrentControlSurfaceDeflection( "Aileron2", 0.15 );

When only using the above, the control surfaces are set to a costant deflection throughout the propagation. This may not be very realistic but can be useful for preliminary analysis.

In general, however, you will want to determine the control surface deflections as a function of your current state, time, etc. The best way to achieve this is by incorporating the control surface deflections into the aerodynamic guidance, in particular into your specific derived class of AerodynamicGuidance (see above). As an example, consider the following:

class FlightConditionsBasedAerodynamicExtendedGuidance: public AerodynamicGuidance
{
    FlightConditionsBasedAerodynamicExtendedGuidance(
            const NamedBodyMap& bodyMap,
            const std::string vehicleName )
    {
        vehicleFlightConditions_ =
           std::dynamic_pointer_cast< AtmosphericFlightConditions >(
              bodyMap.at( vehicleName )->getFlightConditions( ) );
        if( vehicleFlightConditions_ == nullptr )
        {
            throw std::runtime_error( "Error in FlightConditionsBasedAerodynamicGuidance, expected AtmosphericFlightConditions" );
        }
        vehicleSystems_ = bodyMap.at( vehicleName )->getVehicleSystems( );
    }

    void updateGuidance( const double currentTime );

private:

    std::shared_ptr< AtmosphericFlightConditions > vehicleFlightConditions_;

    std::shared_ptr< system_models::VehicleSystems > vehicleSystems_;

};

Compared to the FlightConditionsBasedAerodynamicGuidance class defined above, you can see that it has been extended with the ability to access the VehicleSystems member of the associated body. In the implementation of the updateGuidance function, the control surface deflections may now be incorporated as follows:

void FlightConditionsBasedAerodynamicExtendedGuidance::updateGuidance( const double currentTime )
{
    if( vehicleFlightConditions_->getCurrentAltitude( ) > 60.0E3 )
    {
        currentAngleOfAttack_ = 35.0 * mathematical_constants::PI / 180.0;
    }
    else if( vehicleFlightConditions_->getCurrentAltitude( ) < 25.0E3 )
    {
        currentAngleOfAttack_ = 5.0 * mathematical_constants::PI / 180.0;
    }
    else
    {
        currentAngleOfAttack_ = ( 5.0 + 30.0 * ( vehicleFlightConditions_->getCurrentAltitude( ) - 25.0E3 ) / 35.0E3 ) * mathematical_constants::PI / 180.0;

    }

    currentAngleOfSideslip_ = 0.0;

    if( vehicleFlightConditions_->getCurrentMachNumber( ) < 8 )
    {
        currentBankAngle_ = 80.0 * mathematical_constants::PI / 180.0;
    }
    else
    {
        currentBankAngle_ = 0.0;
    }

    double elevonDeflection = ( 1.0 + 5.0 * ( vehicleFlightConditions_->getCurrentAltitude( ) - 25.0E3 ) / 35.0E3 ) * mathematical_constants::PI / 180.0;
    double aileron1Deflection = ( 2.0 + 7.0 * ( vehicleFlightConditions_->getCurrentAltitude( ) - 25.0E3 ) / 35.0E3 ) * mathematical_constants::PI / 180.0;
    double aileron2Deflection = -( 2.0 + 7.0 * ( vehicleFlightConditions_->getCurrentAltitude( ) - 25.0E3 ) / 35.0E3 ) * mathematical_constants::PI / 180.0;

    vehicleSystems_->setCurrentControlSurfaceDeflection( "Elevon", elevonDeflection );
    vehicleSystems_->setCurrentControlSurfaceDeflection( "Aileron1", aileron1Deflection );
    vehicleSystems_->setCurrentControlSurfaceDeflection( "Aileron2", aileron2Deflection );

}

As with the previous examples, the values to which the control surface deflections are set are quite arbitrary and not based on any particularly realistic model. They are defined for illustration purposes only. A key difference between the manner in which the aerodynamic angles and the control surface deflections are handled by the guidance object is that the angles are computed but not set by the object (the angles are retrieved and set in the body model by the AerodynamicAngleCalculator). The control surface deflections on the other hand are both computed and set by the guidance object.