Basics¶
No external packages or functions are used in the MATLAB interface. All the necessary code is available in the directory tudatBundle/matlabInterface/MatlabInterface
or through the use of built-in MATLAB functions. The MATLAB interface uses the built-in functions jsondecode, jsonencode and split, which are only available since version R2016b.
Code structure¶
The contents of the MATLAB interface are structured in three directories:
- Examples: contains example scripts.
- MatlabInterface: contains the source code. Includes class definitions for creating settings objects, as well as some packages (directories starting with “+”).
- UnitTests: contains scripts used to generate input files for the JSON Interface.
Additionally, these three files can be found in the root directory:
- setup.m: adds the directory
tudatBundle/matlabInterface
to the user’s MATLAB path definition permanently.- build.m: builds all the required targets (using CMake and the C++ code in the tudatBundle) for the MATLAB interface to work properly.
- tudat.m: defines the class
tudat
that is used to include all the source directories in the current session’s path, amongst other things.
The tudat
class¶
After running setup.m
(to be done only once), one can write tudat.load()
(in the current or future MATLAB sessions). In this way, all the classes and functions inside tudatBundle/matlabInterface/MatlabInterface
become available directly through their name, except for the code contained in package directories, which has to be accessed through packageName.functionOrClassName(...)
.
It is also possible to run all the unit tests associated to the MATLAB/JSON interfaces by calling tudat.test()
. This class also stores some paths, such as the absolute path to the MATLAB interface, its source directory, its unit tests directory or the (optional) file settings.mat
, created when the user defines custom paths for the tudatBundle and associated targets. By default, the tudatBundle
directory is the parent directory of the matlabInterface
directory, but this can be changed by calling tudat.find(absolutePathToTudatBundle)
.
The tudat
class can also be used to set/get user settings, stored in the file settings.mat
:
properties (Constant, Hidden)
...
bundlePathKey = 'bundlePath'
defaultBundlePath = fileparts(tudat.rootdir);
...
end
...
methods (Static, Hidden)
function path = bundle(newPath)
if nargin == 0 % get
try
path = tudat.settings.(tudat.bundlePathKey);
catch
path = tudat.defaultBundlePath;
end
else % set
updateSetting(tudat.bundlePathKey,newPath);
end
end
...
end
If the settings named bundlePath
is not stored in the file settings.mat
, or if this file does not exist, the default bundle path will be returned when calling tudat.bundle
. The setting can be modified by calling tudat.bundle(customPath)
. In the same way, support for additional user settings can be added in the future. Note that these properties and methods are usually hidden, as the user will rarely use them. The settings that can be currently customised are: bundlePath
, binaryPath
(the path to the binary json_interface
), testsSourcesDirectoryPath
(the path to the directory containing the JSON Interface unit tests C++ sources) and testsBinariesDirectoryPath
(the path to the directory containing the C++ unit tests binaries).
Deserialisation (decoding)¶
The built-in function jsondecode
fails when the provided text to be decoded (or deserialised) contains line breaks. A custom json.decode
function, located in MatlabInterface/+json/decode.m
has been written to bypass this limitation.
However, currently the MATLAB interface does not support creating settings objects from JSON text. This means that, if one decodes the string {"mass": 5000, "referenceArea": 10}
, a struct will be obtained, but not an object of Body
class. To add support for creating settings objects from JSON text, it would be necessary to write custom constructor implementations or static functions for all the classes in the MatlabInterface
directory or to provide this for the jsonable
class, from which all the settings objects classes derive, in a similar way as done for serialisation.
Serialisation (encoding)¶
Serialisation consists in converting MATLAB variables to JSON-formatted text. This is done using the built-in function jsonencode
. This function generates a single-line text, which is difficult to read for long files. Thus, a custom json.encode
function, located in MatlabInterface/+json/encode.m
, has been written. This function has a second input argument, tabsize
, used to determine the number of spaces for each indentation level. For instance, a value of 2
adds line breaks and uses two space for each indentation level. When not specified, the value of 2
is used. When set to 0
, no spaces or line breaks are added, resulting in a single-line text.
Before encoding an object into JSON-formatted text, it is necessary to convert that object to something that is serialisable, basically to a number, string, boolean or array/map containing these objects. This is done by using the function json.jsonize
. For numbers, strings and booleans, this function does nothing, as these types are directly serialisable. For structs and cell arrays, this function jsonizes each of their elements. The built-in encoding function only supports containers.Map
when its keys are strings, so json.jsonize
extends this functionality by converting the keys of non-char containers.Map
to string by using the format specification %g
. The most important addition of the json.jsonize
is, however, support for custom classes and enumerations.
Enumerations¶
For enumerations, the name of the enumeration value is used. This means that nothing has to be added to the enumeration definition for it to be serialisable. For instance, consider the following enumeration:
classdef RotationModels
enumeration
simple
spice
end
end
If we use the built-in function and write jsonencode(RotationModels.simple)
we get:
Error using jsonencode
Unable to encode objects of enumeration class RotationModels as JSON-formatted text.
However, if we use the custom function and write json.encode(RotationModels.simple)
we get the text "simple"
. Writing json.jsonize(RotationModels.simple)
returns the char variable simple
, i.e. json.jsonize
converts a variable to something that is serialisable, while json.encode
actually serialises it.
Custom classes¶
For classes, the serialisation process is a bit more complex. Although the built-in function supports encoding objects of custom classes, by converting them first to a struct in which each of its field names corresponds to a class’ property, this fails when any of the properties is set to be an object that is not supported by jsonencode
, such as an enumeration value. Additionally, in some cases it may not be necessary to use all the properties of a class in its JSON representation. Thus, for the classes used in the MATLAB interface, we define a jsonize
method, which is then called by the json.jsonize
function. This means that this function, as well as json.encode
, do not support custom classes that do not implement the method jsonize
.
Rather than implementing a custom jsonize
method for all the custom classes, all the classes that should be convertible to JSON were made to derive from the class jsonable
, defined in MatlabInterface/jsonable.m
, which derives from the built-in class handle
(similar to a shared pointer, i.e. if a handle
is modified, all the objects containing it will also be updated automatically).
The jsonable
class has the following (hidden) methods:
getProperties
: returns the list of properties to be used for the JSON representation. This list includes all the properties of the (derived) class on which it is called that are not marked as Transient. For instance, for theBody
class:classdef Body < jsonable properties useDefaultSettings initialState mass rotationalState referenceArea aerodynamics atmosphere ephemeris gravityField gravityFieldVariation radiationPressure rotationModel shapeModel end properties (Transient) name end ... end
when it is converted to JSON, the property
name
is ignored, because it is used internally by the MATLAB interface but it is not needed by the JSON interface or Tudat.isMandatory(property)
: returns whether the property namedproperty
is mandatory. If a property is marked as mandatory, but its value is empty, thejson.encode
function will fail. In order to know which properties are mandatory, the derived classes have to implement the (hidden) methodgetMandatoryProperties
. For instance, forBody
:classdef Body < jsonable ... methods (Hidden) function mp = getMandatoryProperties(obj) mp = {}; end end end
In this case, none of the properties of
Body
are mandatory, because for a non-perturbed problem, we do not need to define any of its properties, so an empty body (i.e.[]
ornull
) is valid. In other cases, some of the properties are mandatory:classdef Propagator < jsonable ... methods (Hidden) function mp = getMandatoryProperties(obj) mp = {'integratedStateType','bodiesToPropagate'}; end end end
classdef MassPropagator < Propagator ... methods (Hidden) function mp = getMandatoryProperties(obj) mp = getMandatoryProperties@Propagator(obj); mp = horzcat(mp,{'massRateModels'}); end end end
Note that, for
MassPropagator
, the mandatory properties are those ofPropagator
, from which it derives, i.e.integratedStateType
andbodiesToPropagate
, and also the propertymassRateModels
. Thus, we get first the mandatory properties of the base class by callinggetMandatoryProperties@Propagator(obj)
, and then add additional mandatory properties for the derived class.isPath(property)
: returns whether the property namedproperty
represents a path. Paths are automatically converted to JSON using the format specification@path(%s)
. By default, this returnsfalse
. Derived classes can override this method:classdef TabulatedAtmosphere < Atmosphere properties file end ... methods (Hidden) function p = isPath(obj,property) p = strcmp(property,'file'); end end end
isempty
: overrides the implementation of thehandle
class, so that an instance ofjsonable
will be empty if (an only if) all of its non-transient properties (i.e. the properties returned bygetProperties
) are empty. There is no need to override this method in derived classes ofjsonable
.jsonize
: returns an object that can be serialised as JSON-formatted text. Basically, it returns a struct containing the values for the non-transient properties (i.e. the properties returned bygetProperties
) that are non-empty. If an optional property has an empty value (e.g.[]
), this field will not be defined in the returned struct. There is no need to provide a custom implementation of this method in derived classes; we only override this method if we want the JSON representation to include keys other than those associated to non-transient properties. This is done only once in the MATLAB interface inMatlabInterface/Termination/Termination.m
Other types¶
Some variables other than custom classes or enumerations are not serialisable either. For instance, complex numbers. These types have to be identified in the function json.jsonize
, a custom implementation can be provided in this file:
function j = jsonize(object)
...
try
j = object.jsonize();
catch ME
...
elseif strcmp(ME.identifier,'MATLAB:structRefFromNonStruct')
if isnumeric(object)
if any(imag(object(:)))
...
First, we try calling the jsonize
method. If it is not defined, a MATLAB:structRefFromNonStruct
error will be thrown. By catching this error, we can e.g. check whether the object is an imaginary number and provide a custom implementation for converting it to a value that is serialisable. For instance, the JSON Interface uses boost::literal_cast
to cast strings to std::complex
, so we provide the required code so that json.jsonize(0.5-4i)
returns the char (0.5,-4)
, which is serialised as the string "(0.5,-4)"
.