.. _matlabInterface_usage: .. role:: jsontype .. role:: jsonkey Usage ===== To use the MATLAB Interface, you first need to include the directories containing the code into your MATLAB's path for the current MATLAB's session. You can do this by writing at the beginning of your script: .. code-block:: matlab tudat.load(); After this line, you can create objects such as :literal:`Integrator()`, :literal:`Body()`, etc, and access the functionality of the packages included in the MATLAB Interface, such as :literal:`+convert`, :literal:`+json`, etc. Before detailing how to set up simulations, run them and obtain the results, it is convenient to make a distinction between two possible usage modes of the MATLAB Interface: - **Seamless mode**. You use the MATLAB Interface to set up simulations that will be run directly from your MATLAB script. Temporary input and output files will be generated and deleted by the MATLAB Interface in the background. When the simulation completes, you will be able to access the results directly in the struct :literal:`results` of your :class:`Simulation` object. You can use this data to generate plots and eventually consolidate (parts of) it in a text file. - **Input-output mode**. You use the MATLAB Interface to set up simulations and to generate JSON input files that will then be provided to the :literal:`json_interface` manually, e.g. from the command line or in Qt Creator. You must specify the output files to be generated by using the :literal:`addResultsToExport` method of your :class:`Simulation` object. Then, the generated output files can be opened with your favourite text editor and/or loaded into MATLAB for post-processing and plotting. These modes are not mutually exclusive, i.e. you can run your simulations from MATLAB and still get to keep the input and output files in your directory. In the following sections, the steps to be followed for each of these modes are described. The first part, setting up the simulation, is generally identical for the two usage modes. Setting up the simulation ~~~~~~~~~~~~~~~~~~~~~~~~~ The settings for a Tudat simulation are defined by creating a :class:`Simulation` object in MATLAB. Then, this object is used to create a JSON input file that will be provided to the :literal:`json_interface` application, either manually by the user or internally by MATLAB. The properties that can be defined for this :class:`Simulation` object are very similar to those that can be defined for a JSON Interface. You can read the :ref:`jsonInterface_keys` for an exhaustive list of possible keys. Here, only the most frequently-used features, and those that in the MATLAB Interface are set up in a different way, will be discussed. The :class:`Simulation` object can be created by writing: .. code-block:: matlab simulation = Simulation(); It is also possible to provide up to four parameters to the constructor: .. code-block:: matlab simulation = Simulation(0,86400,'SSB','J2000'); which is equivalent to: .. code-block:: matlab simulation = Simulation(); simulation.initialEpoch = 0; simulation.finalEpoch = 86400; simulation.globalFrameOrigin = 'SSB'; simulation.globalFrameOrientation = 'J2000'; The documentation for the properties :jsonkey:`initialEpoch`, :jsonkey:`finalEpoch`, :jsonkey:`globalFrameOrigin` and :jsonkey:`globalFrameOrientation` can be found in :ref:`jsonInterface_keys`. Providing Spice settings ************************ Spice is used to determine properties of celestial bodies, such as gravitational parameter, radius, ephemeris, etc. When creating a :class:`Simulation`, the property :jsonkey:`spice.useStandardKernels` will be :literal:`true` by default, so if you want to use the standard Spice kernels you do not need to write any additional code in your MATLAB script. If you do not need to use Spice, you can disable it by writing: .. code-block:: matlab simulation.spice = []; By default, Tudat will try to preload the ephemeris of the bodies with the property :jsonkey:`useDefaultSettings` set to :literal:`true` from :jsonkey:`initialEpoch` to :jsonkey:`finalEpoch`. To prevent this from happening, one can write: .. code-block:: matlab simulation.spice.preloadEphemeris = false; This is necessary when no time-termination condition is provided (i.e. when :jsonkey:`finalEpoch` is not specified, and the propagation will terminate based on other conditions such as altitude). For propagations in which *there is* a time-termination condition but the probability of meeting another termination condition before reaching the maximum final epoch is large, disabling preload of ephemeris may result in faster propagations. For all other cases, preloading the ephemeris is usually faster than retrieving them from Spice at every integration step. Providing body settings *********************** A body is created by writing: .. code-block:: matlab body = Body('Asterix'); which creates a :class:`Body` named :literal:`Asterix`. A second argument can be provided to the constructor to specify whether the default settings for that body should be loaded. This is only valid when the name of the body is recognised by Tudat/Spice (e.g. :literal:`Sun`, :literal:`Earth`, :literal:`Moon`, :literal:`Mars`, etc.). If this second argument is omitted, all the settings for that body are provided manually through the MATLAB Interface. When :jsonkey:`useDefaultSettings` is set to :literal:`true`, but also some settings are provided manually, first the default settings will be loaded when running Tudat, and then the other settings specified manually will be used, potentially overriding some of the default settings that were loaded. For the Sun, the Moon and the planets, the MATLAB Interface provides predefined bodies (basically, :class:`Body` objects with the property :jsonkey:`useDefaultSettings` set to :literal:`true` and the corresponding name already set). These bodies are readily accessible by writing e.g.: .. code-block:: matlab earth = Earth(); Then, we can provide additional settings by writing: .. code-block:: matlab earth.atmosphere.type = AtmosphereModels.nrlmsise00; Finally, when all the bodies needed for the simulation have been created, they have to be added to the :class:`Simulation` object. We do this by writing e.g.: .. code-block:: matlab simulation.addBodies(Sun,earth,Moon,body); Note that we can directly add celestial bodies for which we have not defined additional properties (in this case, :class:`Sun` and :class:`Moon`). We can also modify the bodies after having added them to :literal:`simulation` by writing: .. code-block:: matlab simulation.bodies.Asterix.mass = 5000; simulation.bodies.Earth.ephemeris = ConstantEphemeris(zeros(6,1)); Note that here we refer to the bodies by their names (:literal:`Asterix`, :literal:`Earth`) and not by the name of the MATLAB variables in which they are stored (:literal:`body`, :literal:`earth`). We can also modify the body objects directly, and :literal:`simulation` will be updated automatically. For instance, these two lines are equivalent to the previous code block and can be written safely after the call to :literal:`addBodies`: .. code-block:: matlab body.mass = 5000; earth.ephemeris = ConstantEphemeris(zeros(6,1)); Providing propagator settings ***************************** We can create a propagator by writing: .. code-block:: matlab propagator = Propagator(); However, this creates a non-functional object, as the propagators supported by Tudat have to be either translational, rotational or mass propagators. The most used propagator is the :class:`TranslationalPropagator`, which is used to propagate the Cartesian state of a body. Thus, we can write: .. code-block:: matlab propagator = TranslationalPropagator(); propagator.bodiesToPropagate = {body}; propagator.centralBodies = {earth}; Since we can propagate the states of several bodies, we have to provide a list (i.e. a cell array of objects) to the property :jsonkey:`bodiesToPropagate`. We also must specify a central body for each of the bodies to be propagated through :jsonkey:`centralBodies`. We can provide either a list of :class:`Body` objects or a list of body names. The following two lines are equivalent to the last two lines of the previous code block: .. code-block:: matlab propagator.bodiesToPropagate = {'Asterix'}; propagator.centralBodies = {'Earth'}; When converting the :class:`Simulation` object (which will contain the propagator) to JSON, the name of the body will be used if a :class:`Body` objects has been provided. For the :literal:`propagator` object to be valid, we need to specify the accelerations acting on the different bodies. For instance, for an unperturbed motion: .. code-block:: matlab propagator.accelerations.Asterix.Earth = {PointMassGravity()}; In this case, the only acceleration is the Earth's point-mass gravity acting on the body Asterix. Note that here we also use the names of the bodies. The key :jsonkey:`accelerations.Asterix.Earth` is read as "accelerations on Asterix caused by Earth". We can create additional acceleration objects, such as: .. code-block:: matlab propagator.accelerations.Asterix.Earth = {SphericalHarmonicGravity(5,5), AerodynamicAcceleration()}; propagator.accelerations.Asterix.Sun = {PointMassGravity(), RadiationPressureAcceleration()}; propagator.accelerations.Asterix.Moon = {PointMassGravity()}; The constructor of :class:`SphericalHarmonicGravity` takes the maximum degree and order of the spherical harmonic expansion as arguments. For the rest of accelerations, no input argument are needed. In more complex examples, we can provide additional accelerations such as :literal:`Thrust`, :literal:`MutualSphericalHarmonicGravity`, :literal:`RelativisticCorrectionAcceleration` or :literal:`EmpiricalAcceleration`, which do require additional information in general to result in valid propagations. Finally, when the propagator has been created, we can assign it to the :class:`Simulation` object: .. code-block:: matlab simulation.propagators = {propagator}; Here we provide a list of propagators, since we may want to propagate several states simultaneously. For instance, we may want to propagate both the translational state of the body Asterix and its mass, so we would need to use two different propagators: .. code-block:: matlab translationalPropagator = TranslationalPropagator(); ... massPropagator = MassPropagator(); ... simulation.propagators = {translationalPropagator, massPropagator}; Providing integrator settings ***************************** We can create a fixed step-size :class:`Integrator` by writing: .. code-block:: matlab integrator = Integrator(); integrator.type = Integrators.rungeKutta4; integrator.stepSize = 10; If we want to use a variable step-size integrator, we can write: .. code-block:: matlab integrator = VariableStepSizeIntegrator(RungeKuttaCoefficientSets.rungeKuttaFehlberg78); integrator.initialStepSize = 20; integrator.minimumStepSize = 5; integrator.maximumStepSize = 1e4; integrator.errorTolerance = 1e-11; Setting the property :literal:`errorTolerance` sets both the :jsonkey:`relativeErrorTolerance` and :jsonkey:`absoluteErrorTolerance` keys. Then, we add the integrator to the :class:`Simulation` object: .. code-block:: matlab simulation.integrator = integrator; Since a fixed step-size RK4 integrator is used by default, we can simply write this line to provide all the necessary integrator settings: .. code-block:: matlab simulation.integrator.stepSize = 10; The other required key, :jsonkey:`integrator.initialEpoch`, is retrieved from the property :literal:`initialEpoch` of the :class:`Simulation` object, if defined. Requesting results ****************** In order to define the variables whose values have to be either exported to output files and/or loaded into MATLAB after running the propagation, we need to define Tudat variables in MATLAB. There exist four fundamental variable types: :literal:`independent`, :literal:`state`, :literal:`cpuTime` and :literal:`dependent`. The independent variable is typically the epoch (in seconds since J2000), the state is a vector containing all the states of all the bodies being propagated and the CPU time variable represents the cumulative computation time up to each integration step. There exist many dependent variables whose value can be saved, such as altitude, Mach number, relative position, etc. Creating a Tudat variable in MATLAB can be done in several ways: .. code-block:: matlab epoch = Variable('independent'); state = Variables.state; For dependent variables, additional information is required. For instance, to create a variable representing the relative velocity of the body named Asterix w.r.t. Earth we write: .. code-block:: matlab v = Variable(); v.body = 'Asterix'; v.dependentVariableType = DependentVariables.relativeVelocity; v.relativeToBody = 'Earth'; Note that, when we create a variable with an empty constructor, it is assumed to be of dependent type. However, in the MATLAB Interface, there is a shortcut for defining Tudat variables. The previous block of code is equivalent to: .. code-block:: matlab v = Variable('Asterix.relativeVelocity-Earth'); A few dependent variables, such as :literal:`acceleration` or :literal:`accelerationNorm`, require additional information, namely the type of acceleration. For instance, if we want to define variables representing the aerodynamic and radiation pressure accelerations caused by Earth and the Sun on Asterix, we can write: .. code-block:: matlab drag = Variable('Asterix.acceleration@aerodynamic-Earth'); srp = Variable('Asterix.acceleration@cannonBallRadiationPressure-Sun'); For vectorial variables, if we are only interested in one of the components, we can add the index of the component we want at the end. For instance, for the x-component of aerodynamic drag: .. code-block:: matlab drag_x = Variable('Asterix.acceleration@aerodynamic-Earth[0]'); drag_x = Variable('Asterix.acceleration@aerodynamic-Earth(1)'); Note that, when using the C++ syntax, i.e. :literal:`[index]`, the indices start from 0, while when using the MATLAB syntax, i.e. :literal:`(index)`, the indices start from 1. Thus, the two previous lines are equivalent. Once that we have created the variables that we want to compute, we can configure the :class:`Simulation` object to export their values to an output file (for each integration step). We do this by writing: .. code-block:: matlab simulation.addResultsToExport('dragX.txt',{'independent',drag_x}); Here, the first argument is the output file path (relative to the directory where the input JSON file is located, or to the current working directory if run directly from MATLAB) and the second argument is a list of :class:`Result` objects. If a :class:`Variable` or :class:`char` is provided, it will be converted to a :literal:`Result` object with default settings. A :literal:`Result` object can be used to specify whether the values at all the integration steps are wanted (or only the first and/or last), the number of digits, whether the independent variable should be automatically included (which is :literal:`false` by default in the MATLAB interface, hence the need to include the variable :literal:`independent` in the list). For instance, we can write: .. code-block:: matlab result = Result(); result.variables = {Variable('Asterix.acceleration@aerodynamic-Earth(1)')}; result.epochsInFirstColumn = true; result.numericalPrecision = 10; result.onlyFinalStep = true; simulation.addResultsToExport('finalDragX.txt',result); to generate an output file containing the X component of the aerodynamic acceleration only at the last step, together with the final epoch. If we do not want to export the results to an output file, we can write: .. code-block:: matlab simulation.addResultsToSave('dragX',drag_x); where the first argument is the name of the MATLAB variable containing the results (after running the simulation) and the second argument is (a list of) result/variable(s). Behind the scenes, this will ask Tudat to create a temporary output file containing the results, which will then be loaded by the MATLAB interface into the struct :literal:`simulation.results` and deleted. Thus, after the call to :literal:`simulation.run()`, the drag can be obtained at :literal:`simulation.results.dragX`. Defining termination conditions ******************************* For the propagation to terminate, we have to define termination conditions; otherwise, it will go on forever or terminate with an error when undefined behaviour is reached (e.g. the satellite reaches infinite velocity, its altitude goes below zero, etc.). It is possible to define termination conditions for any :class:`Variable` object. The time-termination condition is based on a limit for the value of the independent variable. However, we need not specify this condition manually; it will be created automatically by Tudat if the property of :literal:`finalEpoch` of the :class:`Simulation` object has been defined. In some case, we want to provide additional conditions, such as terminating the propagation when the satellite's altitude goes below 100 km. We can do this by writing: .. code-block:: matlab simulation.termination = Variable('Asterix.altitude-Earth') < 100000; Note that the operators :literal:`<=`, :literal:`>=` and :literal:`==` are not defined for :class:`Variable` objects, so we always have to use either :literal:`<` or :literal:`>`. We can also provide multiple termination conditions by using the operators :literal:`&` and :literal:`|` (note that :literal:`&&` and :literal:`||` won't work): .. code-block:: matlab condition1 = Variable('Asterix.altitude-Earth') < 50000; condition2 = Variable('Asterix.altitude-Earth') < 80000; condition3 = Variable('Asterix.machNumber') > 20; simulation.termination = condition1 | ( condition2 & condition3 ); In any case, if :literal:`simulation.finalEpoch` has been defined, a time-based condition will be added to the provided conditions, so these three blocks of code are equivalent: .. code-block:: matlab simulation.finalEpoch = 1e5; simulation.termination = Variable('Asterix.machNumber') < 1; .. code-block:: matlab simulation.termination = Variable('Asterix.machNumber') < 1 | Variable('independent') > 1e5; .. code-block:: matlab simulation.finalEpoch = 1e5; simulation.termination = Variable('Asterix.machNumber') < 1 | Variable('independent') > 1e5; Defining application options **************************** It is possible to specify options for the :literal:`json_interface` application called by MATLAB or called directly by the user with the JSON files generated by MATLAB as input files by changing the properties of :literal:`simulation.options`: .. code-block:: matlab simulation.options.fullSettingsFile = 'fullSettings.json'; For instance, this is asking the application to generate a JSON file containing all the settings (those provided in the input file(s) and the default values and settings loaded from Tudat). This file will be generated when running the :literal:`json_interface` application, right before integrating the equations of motion. Running the simulation ~~~~~~~~~~~~~~~~~~~~~~ If you want to call the :literal:`json_interface` from MATLAB, you can simply write: .. code-block:: matlab simulation.run(); This will generate a temporary JSON input file, use it to run :literal:`json_interface`, load the generated output files, and delete all the temporary input and output files that have been generated. If you want to run the simulation manually, you will have to export the JSON file containing the settings for the simulation. You do this by using the :literal:`json` package of the MATLAB Interface: .. code-block:: matlab json.export(simulation,'main.json'); which exports the object :literal:`simulation` to the file :class:`main.json`. Then, you can call the :literal:`json_interface` application: .. code-block:: matlab json_interface main.json which will generate the output files specified in the key :jsonkey:`export`. For generating `modular files <../jsonInterface/modularFiles.html>`_, one can write e.g.: .. code-block:: matlab integrator = ... json.export(integrator,'rk4.json'); simulation = ... simulation.integrator = '$(rk4.json)'; json.export(simulation,'main.json'); Which will generate the file :class:`rk4.json` containing only the integrator settings, and the file :class:`main.json` containing the remainder of the settings, and a reference to the integrator file by using the special string :literal:`$(rk4.json)`. The previous block of code is equivalent to: .. code-block:: matlab integrator = ... simulation = ... simulation.integrator = json.modular(integrator,'rk4.json'); json.export(simulation,'main.json'); The function :literal:`json.modular` exports the object :literal:`integrator` to the file :class:`rk4.json` and returns the string :literal:`$(rk4.json)`, which is assigned to :literal:`simulation.integrator`. For `multi-case propagations <../jsonInterface/multicase.html>`_, one can write e.g.: .. code-block:: matlab simulation = ... body = ... simulation.addBodies(body,...); json.export(simulation,'shared.json'); for i = 1:10 m = i*1000; json.export(json.merge('$(shared.json)','bodies.Asterix.mass',m),sprintf('mass%i.json',m)); end which generates the file :class:`shared.json` containing the shared settings, and 10 files such as: .. code-block:: json :caption: :class:`mass1000.json` [ "$(shared.json)", { "bodies.Asterix.mass": 1000 } ] If no output files are to be generated (seamless mode), the same behaviour can be achieved just by writing: .. code-block:: matlab simulation = ... body = ... simulation.addBodies(body,...); for i = 1:10 body.mass = i*1000; simulation.run(); results{i} = simulation.results; end Accessing the results ~~~~~~~~~~~~~~~~~~~~~ After running the propagation, the results can be retrieved from the generated output files, or directly from the struct :literal:`results` of the :class:`Simulation` object. In addition to the results requested to be saved in :literal:`simulation.results` by using the method :literal:`addResultsToSave`, it is always possible to access :literal:`simulation.results.numericalSolution`, which is a matrix containing, for each step, the epoch in the first column and the states (of all the bodies) in subsequent columns. The function called internally by the MATLAB Interface to load the results from the temporary output files that have been generated is :literal:`import.results`. This is the same function that should be used when importing the results from output files generated by running the :literal:`json_interface` manually: .. code-block:: matlab [results,failure] = import.results('output.txt'); This function reads the contents of the output file into the matrix :literal:`results` and can also return a second argument, :literal:`failure`, which will be :literal:`true` if the propagation terminated before reaching the termination condition. If an error occurred during propagation, the results will only be available until the integration step previous to that error. In that case, the JSON Interface adds the header :literal:`FAILURE` to the generated output files, which is detected by the :literal:`import.results` function. When calling :literal:`simulation.run()` and loading the results automatically into MATLAB, the information on whether the propagation failed is not available, but it is not needed because a message will be printed to the command window in case that a propagation error is encountered. However, when running many propagations e.g. on a server using the :literal:`json_interface` application, it is possible that the only remaining consolidated information be the output file and not the command-line warnings/errors. Thus, in that case, the presence of the :literal:`FAILURE` header in the output files *is* useful. To turn off this feature (i.e. to prevent output files from including a line containing the text :literal:`FAILURE` when a propagation error is encountered), one can write: .. code-block:: matlab simulation.options.tagOutputFilesIfPropagationFails = false; In that case, it is safe to import the results by calling built-in MATLAB functions such as :literal:`load` or :literal:`importdata`. In addition to the output files, after calling :literal:`simulatin.run()`, it is possible to retrieve the actual settings that have been used for the propagation (including all the default values) by writing :literal:`simulation.fullSettings`.