3.4. Perturbed Earth-orbiting Satellite Lifetime Maximisation Using a Custom C++ Application

In this tutorial, the creation of a custom JSON-based application is described. Read first the general introduction on Writing Custom JSON-based Applications.

The example described on this page is identical to that described in Perturbed Earth-orbiting Satellite Lifetime Maximisation. The only difference is that there, the json_interface application is used with many input files to run the different cases, while in this case only one input file, containing the shared settings, is used, and a custom C++ application is written to manually modify a few parameters for each case. The C++ code can be found in:

tudatBundle/tudatExampleApplications/satellitePropagatorExamples/SatellitePropagatorExamples/lifetimeMaximisation.cpp

The file lifetimeMaximisation.json used as input for all the propagations is identical to the file shared.json created in Perturbed Earth-orbiting Satellite Lifetime Maximisation.

In this case, we do not write a C++ application because we want to use Tudat features that cannot be provided through a JSON file, but because we want to avoid having to generate a different input file for each propagation. The disadvantage of this choice is that we cannot use parallel processing, as all the propagations will be run sequentially by a single process, while when using the json_interface with different input files, we are able to run many cases concurrently.

In this case there is no need to write a derived class of JsonSimulationManager with custom implementations for virtual methods. We can simply modify the JSON object containing the settings read from the JSON file before it is actually used to set up the simulation objects. Thus, all the code we need to write is:

 1#include <Tudat/JsonInterface/jsonInterface.h>
 2#include <SatellitePropagatorExamples/applicationOutput.h>
 3
 4int main( )
 5{
 6  const std::string cppFilePath( __FILE__ );
 7  const std::string cppFolder = cppFilePath.substr( 0, cppFilePath.find_last_of("/\\") + 1 );
 8  tudat::json_interface::JsonSimulationManager< > jsonSimulationManager( cppFolder + "lifetimeMaximisation.json" );
 9
10  const std::string outputDirectory = tudat_applications::getOutputPath( ) + "LifetimeMaximisation/";
11  const unsigned int numberOfCases = 365;
12  for ( unsigned int i = 0; i < numberOfCases; ++i )
13  {
14      // Notify on propagation start
15      std::cout << "Running propagation " << i + 1 << " of " << numberOfCases << "..." << std::endl;
16
17      // Define the initial and final epochs
18      const double initialEpoch = i * tudat::physical_constants::JULIAN_DAY;
19      jsonSimulationManager[ "initialEpoch" ] = initialEpoch;
20      jsonSimulationManager[ "finalEpoch" ] = initialEpoch + tudat::physical_constants::JULIAN_YEAR;
21
22      // Define the output file
23      jsonSimulationManager[ "export" ][ 0 ][ "file" ] = outputDirectory + "day" + std::to_string( i + 1 ) + ".txt";
24
25      // Create settings objects
26      jsonSimulationManager.updateSettings( );
27
28      // Propagate
29      jsonSimulationManager.runPropagation( );
30
31      // Export results
32      jsonSimulationManager.exportResults( );
33
34      // Silence unused key warnings after first propagation
35      if ( i == 0 )
36      {
37          jsonSimulationManager[ "options" ][ "unusedKey" ] = tudat::json_interface::continueSilently;
38      }
39  }
40
41  return EXIT_SUCCESS;
42}

In this example, we first create a JsonSimulationManager, which will contain the JSON object obtained by parsing the specified input file. Then, we write a loop in which we modify this JSON object before it is actually used to set up the objects needed for the propagation (integrator, propagators, bodies, etc.) when we call the updateSettings method. We can access this JSON object by using the method getJsonObject. However, we can also modify this object by accessing the at method and the [ ] operators of the JsonSimulationManager directly. Thus, these two lines are equivalent:

jsonSimulationManager.getJsonObject( )[ "initialEpoch" ] = 0;
jsonSimulationManager[ "initialEpoch" ] = 0;

And so are these two lines too:

std::cout << jsonSimulationManager.getJsonObject( ).at( "initialEpoch" ) << std::endl;
std::cout << jsonSimulationManager.at( "initialEpoch" ) << std::endl;

Inside the loop, in which we iterate for each of the propagations to be carried out, we modify the keys initialEpoch and finalEpoch of the JSON object. Additionally, we want each propagation to generate an output file with a unique name, so we also modify the key export[ 0 ].file. Then, we can set up the simulation, run the propagation and export the results.

After the first propagation has been completed, we turn off warnings for unused keys. This is done to silence warnings about the key bodies.satellite.initialState being unused. When running the first propagation, the Keplerian state defined in bodies.satellite.initialState is converted to Cartesian and assigned to propagators[ 0 ].initialStates. Further propagations find that the key propagators[ 0 ].initialStates is defined, and thus they do not use the information at bodies.satellite.initialState, resulting in an unused key warning if we do not set the key options.unusedKey to "continueSilently".