6.3. Simulating Observations¶
6.3.1. Creating the Observation Simulator¶
In Tudat, a set of observations are simulated using the ObservationSimulatorBase
class. An object of this class is used to simulate observations of a single type, for any number of LinkEnds
. The ObservationSimulatorBase
has, like many of the classes in Tudat a number of template arguments, one for the scalar type of the observable and one for the time type. Note that the state scalar used in numerical propagation should be equal to the scalar type of the observable to make full use of the functionality.
There are two ways in which to obtain ObservationSimulatorBase
objects:
When creating a
OrbitDeterminationManager
class, a set ofObservationSimulatorBase
objects are automatically created. Retrieving these is done simply by:OrbitDeterminationManager< ObservationScalarType, TimeType > orbitDeterminationManager = ..... //OrbitDeterminationManager object created here. std::map< ObservableType, boost::shared_ptr< ObservationSimulatorBase< ObservationScalarType, TimeType > > > observationSimulators = orbitDeterminationManager.getObservationSimulators( );
This returns the
observationSimulators
list, which contains an observation simulator for eachObservableType
defined in your orbit determination manager.You can also create an
ObservationSimulatorBase
directly, without using anOrbitDeterminationManager
object.// Define type of observable ObservableType observableType = .... // Define observation settings for each require LinkEnds std::map< LinkEnds, boost::shared_ptr< ObservationSettings > > settingsPerLinkEnds = .... // Define environment NamedBodyMap bodyMap = .... // Create observation simulator boost::shared_ptr< ObservationSimulatorBase< ObservationScalarType, TimeType > > observationSimulator = createObservationSimulator( observableType, settingsPerLinkEnds, bodyMap );
In either case, this provides you with an object of type ObservationSimulatorBase
.
6.3.2. Observation Simulation Settings¶
The times at which observations are simulated may be defined directly by the user, or they may depend on some scheduling algorithm, which is then used to determine the observation times. The final observation times are determined by a combination of ObservationSimulationTimeSettings
objects and ObservationViabilitySettings
objects. The former allows you to define observation times directly, or an observation schedule algorithm. The latter defines constraints that must be met for an observation to be possible. The viability settigns are discussed on the page Observation Viability Setttings.
Each type of observation settings is defined by a dedicated derived class of ObservationSimulationTimeSettings
. This class has a TimeType
argument, which is discussed in more detail tudatTemplatingStateTime. The following classes are presently available:
-
class
TabulatedObservationSimulationTimeSettings
¶ The
TabulatedObservationSimulationTimeSettings
class is used to define a simple list of times at which observations are simulated.boost::shared_ptr< TabulatedObservationSimulationTimeSettings< TimeType > > observationSettings = boost::make_shared< TabulatedObservationSimulationTimeSettings< TimeType > >( linkEndType, simulationTimes );
The input is:
linkEndType
A
LinkEndType
variable denoting the reference link end type for the observation times.simulationTimes
A
std::vector< TimeType >
variable, containing the list of times at which observations are to be simulated.
6.3.3. Observation Viability Setttings¶
In many cases, you will not have the list of observation times a priori. Instead, the observation times could be a function of the states of the link ends, and depend on a number of constraints that must be satisfied for an observation to be possible. The constraints defined in Tudat are listed in the ObservationViabilityType
enum, which can take the following values:
minimum_elevation_angle
: Minimum elevation angle at a ground station: target must be at least a certain elevation above the horizon.body_avoidance_angle
: Body avoidance angle: the line-of-sight vector from a link end to a given third body must have an angle w.r.t. the line-of-sight between link ends that is sufficiently large. This constraint is typically used to prevent the Sun from being too close to the field-of-view of the telescope(s).body_occultation
: Body occultation: the link must not be obscured by a given third body. For instance: the Moon occulting a link between Earth and Mars.
In Tudat, such constraints are defined by objects of the ObservationViabilitySettings
class.
-
class
ObservationViabilitySettings
¶ The
ObservationViabilitySettings
class is used to define a simple list of times at which observations are simulated.boost::shared_ptr< ObservationViabilitySettings > observationViabilitySettings = boost::make_shared< ObservationViabilitySettings >( observationViabilityType, associatedLinkEnd, stringParameter, doubleParameter );
The input is:
observationViabilityType
A
ObservationViabilityType
variable denoting the type of constraint that is to be createdassociatedLinkEnd
A
std::pair< std::string, std::string >
variable, denoting the link end for which the constraint is to be appliedNote
When leaving the second entry of the
associatedLinkEnd
empty (for instancestd::make_pair( "Earth", "" )
, the constraint will be applied for all ground stations on that body.stringParameter
An
std::string
input parameter defining a property of the constraint. Its meaning is different for different constraint types:minimum_elevation_angle
: None (stringParameter must be""
)body_avoidance_angle
: Name of body to which viewing angle should be larger than value defined bydoubleParameter
body_occultation
: Name of body for which occultation is to be taken into account
doubleParameter
A
double
input parameter defining a property of the constraint. Its meaning is different for different constraint types:minimum_elevation_angle
: Minimum value of elevation angle (in radians) at ground stationbody_avoidance_angle
: Minimum value of body viewing angle (in radians) of body that is to be avoided.body_occultation
: None (doubleParameter must beTUDAT_NAN
)
As is the case for many other Tudat functionalities, the actual objects that perform the viability calculcations (of the ObservationViabilityCalculator
class) are created from the settings objects as follows:
// Define environment NamedBodyMap bodyMap = .... ; // Define link ends for each observable std::map< ObservableType, std::vector< LinkEnds > > linkEndsList = .... ; // Define observation viability settings std::vector< boost::shared_ptr< ObservationViabilitySettings > > observationViabilitySettings = .... ; // Create viability calculators PerObservableObservationViabilityCalculatorList viabilityCalculators = createObservationViabilityCalculators( bodyMap, testLinkEndsList, observationViabilitySettings );
Where PerObservableObservationViabilityCalculatorList
is a typedef for std::map< ObservableType, std::map< LinkEnds, std::vector< boost::shared_ptr< ObservationViabilityCalculator > > > >
, which is a list of viability calculators for each set of link ends and observable type.
6.3.4. Observation Noise¶
In addition to the observation biases (see Observation Biases), which are part of the observation model and typically deterministic, stochastic noise may be added to the observations when simulating them.
The interface for observation noise is made general, allowing both time-correlated and time-uncorrelated noise to be added: a function of type boost::function< double( const double ) >
must be created. Here, the function input is the current time, and the output the noise value. You are free to define this function in any way you like. Refer to the documentation of boost::function
and boost::bind
(see Boost: Basic Concepts).
In typical basic simulation studies, time-uncorrelated white noise is used. To easily add this type of noise, you can make use of the Tudat interface to boost probability distributions/random number generation (see Probability Distributions). As an example, the following will generate a function which generates which noise with a mean of 0.005 and a standard deviationof 0.003.
// Define (arbitrary) noise properties
double meanValue = 5.0E-3
double standardDeviation = 3.0E-3;
// Create noise function
boost::function< double( ) > inputFreeNoiseFunction = createBoostContinuousRandomVariableGeneratorFunction(
normal_boost_distribution, boost::assign::list_of( meanValue )( standardDeviation ), 0.0 );
boost::function< double( const double ) > noiseFunction =
boost::bind( &utilities::evaluateFunctionWithoutInputArgumentDependency< double, const double >,
inputFreeNoiseFunction, _1 );
You may use a similar approach to use any of the boost distrbutions for noise. Note that the second step, in which the evaluateFunctionWithoutInputArgumentDependency
is called, is needed for consistency with the observation noise interface.
6.3.5. Generating the observations¶
Before discussing in detail how to generate simulated observations, we need to define the manner in which these observations are return. Presently, they are stored in the following complicated data type:
std::map< ObservableType, std::map< LinkEnds, std::pair< Eigen::Matrix< ObservationScalarType, Eigen::Dynamic, 1 >, std::pair< std::vector< TimeType >, LinkEndType > > > >
The first part of this type :literal:`std::map< ObservableType, std::map< LinkEnds, … ` denotes that a separate set of observations is generated for each requested observable type and set of link ends. For each of these, the simulated data is stored in the following data type:
std::pair< Eigen::Matrix< ObservationScalarType, Eigen::Dynamic, 1 >, std::pair< std::vector< TimeType >, LinkEndType > >
This pair contains:
- A vector with the values of the observables, as a
Eigen::Matrix< ObservationScalarType, Eigen::Dynamic, 1 >
(equal toEigen::VectorXd
whenObservationScalarType = double
).- Another pair, this time:
std::pair< std::vector< TimeType >, LinkEndType >
, which first contains the observation times, and second the reference link end type of these observations (e.g. is the time valiud at reception or transmission of the signal).
For observations of size 1, the Eigen::Vector
of observations and std::vector
of times are the same length. For observations with size larger than 1, however, they are not, with the vector of observations being N times the size of the vector of times (with N the size of a single observable). For instance, for an angular position observable (N=2), entry 0 of the time vector gives the observation time of entry 0 and 1 of the observation vector, entry 1 of the time vector gives the time of entry 2 and 3 of the observation vector, etc.
Using the above, you can create all the required input to generate observations. Note that while the ObservationSimulatorBase
and ObservationSimulationTimeSettings
are required for this, the noise function and viability calculators need not be provided (no noise and no observation constraints are then used). The simplest way to generate observations, without noise or viability checks, is by using the following:
// Define times at which to simulate the observations
std::map< ObservableType, std::map< LinkEnds, boost::shared_ptr< ObservationSimulationTimeSettings< TimeType > > > > observationTimeSettings = .... ;
// Define observation simulator objects
std::map< ObservableType, boost::shared_ptr< ObservationSimulatorBase< ObservationScalarType, TimeType > > > observationSimulators = .... ;
// Define (arbitrary) noise properties
FullSimulatedObservationSet = simulateObservations( observationsToSimulate, observationSimulators );
When including checks on the viability of the observations, this must be extended to:
// Define times at which to simulate the observations
std::map< ObservableType, std::map< LinkEnds, boost::shared_ptr< ObservationSimulationTimeSettings< TimeType > > > > observationTimeSettings = .... ;
// Define observation simulator objects
std::map< ObservableType, boost::shared_ptr< ObservationSimulatorBase< ObservationScalarType, TimeType > > > observationSimulators = .... ;
// Define viability calculators for observations
PerObservableObservationViabilityCalculatorList viabilityCalculatorList = .... ;
// Define (arbitrary) noise properties
FullSimulatedObservationSet = simulateObservations( observationsToSimulate, observationSimulators, viabilityCalculatorList );
Which will limit the simulated observation set to those that comply with the conditions defined by the viabilityCalculatorList
, see THIS PAGE for more details.
Finally, when including noise on the simulated observations, we provide a number of interfaces of varying levels of generality. The interface that provides the greatest degree of freedom is the following:
// Define times at which to simulate the observations
std::map< ObservableType, std::map< LinkEnds, boost::shared_ptr< ObservationSimulationTimeSettings< TimeType > > > > observationTimeSettings = .... ;
// Define observation simulator objects
std::map< ObservableType, boost::shared_ptr< ObservationSimulatorBase< ObservationScalarType, TimeType > > > observationSimulators = .... ;
// Define viability calculators for observations
PerObservableObservationViabilityCalculatorList viabilityCalculatorList = .... ;
// Define observation noise functions
std::map< ObservableType, std::map< LinkEnds, boost::function< Eigen::VectorXd( const double ) > > > noiseFunctions = .... ;
// Define (arbitrary) noise properties
FullSimulatedObservationSet = simulateObservationsWithNoise( observationsToSimulate, observationSimulators, noiseFunctions, viabilityCalculatorList );
Which requires a noise function defined as a Eigen::VectorXd
as a function of time (const double
), where we use a vector representation of the observation noise to allow noise models to be applied to multi-valued observables (e.g. angular position). However, The noiseFunctions
may also be of one of the following:
std::map< ObservableType, std::map< LinkEnds, boost::function< double( const double ) > > >
Here the noise is defined as a single output. If the observable is multi-valued, the same function is called to generate the noise for each of the entries of the observable. Note that the function is called separately for each entry.std::map< ObservableType, boost::function< Eigen::VectorXd( const double ) > >
Here, the noise is not defineed separately for each set ofLinkEnds
, only perObservableType
, the same function is used for each set link ends of a given type of observable.std::map< ObservableType, boost::function< double( const double ) > >
A combination of the previous two input types.boost::function< double( const double ) >
The same noise function is used for each observable, link ends, and observable entry (for multi-valued observables)