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:

MatlabInterface/tudat.m
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:

MatlabInterface/MassRateModel/MassRateModels.m
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 the Body class:

    MatlabInterface/Body/Body.m
    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 named property is mandatory. If a property is marked as mandatory, but its value is empty, the json.encode function will fail. In order to know which properties are mandatory, the derived classes have to implement the (hidden) method getMandatoryProperties. For instance, for Body:

    MatlabInterface/Body/Body.m
    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. [] or null) is valid. In other cases, some of the properties are mandatory:

    MatlabInterface/Propagator/Propagator.m
    classdef Propagator < jsonable
      ...
      methods (Hidden)
          function mp = getMandatoryProperties(obj)
              mp = {'integratedStateType','bodiesToPropagate'};
          end
      end
    end
    
    MatlabInterface/Propagator/MassPropagator.m
    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 of Propagator, from which it derives, i.e. integratedStateType and bodiesToPropagate, and also the property massRateModels. Thus, we get first the mandatory properties of the base class by calling getMandatoryProperties@Propagator(obj), and then add additional mandatory properties for the derived class.

  • isPath(property): returns whether the property named property represents a path. Paths are automatically converted to JSON using the format specification @path(%s). By default, this returns false. Derived classes can override this method:

    MatlabInterface/Body/Atmosphere/TabulatedAtmosphere.m
    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 the handle class, so that an instance of jsonable will be empty if (an only if) all of its non-transient properties (i.e. the properties returned by getProperties) are empty. There is no need to override this method in derived classes of jsonable.

  • 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 by getProperties) 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 in MatlabInterface/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:

MatlabInterface/+json/jsonize.m
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)".