Enumerations

Enumerations are widely used by Tudat to define the type of settings objects. In this section, it will be explained how to implement support for the enumeration EphemerisType. The same process can be followed to add support for other enumerations in the future.

Tudat/SimulationSetup/EnvironmentSetup/createEphemeris.h
...

namespace tudat
{

namespace simulation_setup
{

...
enum EphemerisType
{
    approximate_planet_positions,
    direct_spice_ephemeris,
    tabulated_ephemeris,
    interpolated_spice,
    constant_ephemeris,
    kepler_ephemeris,
    custom_ephemeris
};

...

Built-in implementation

The JSON for modern C++ library has built-in support for enumerations. For instance:

nlohmann::json j = 1;
EphemerisType ephemerisType = j;                      // direct_spice_ephemeris
EphemerisType ephemerisType = interpolated_spice;
nlohmann::json j = ephemerisType;
std::cout << ephemerisType << std::endl;              // 3

However, the built-in implementation uses the value of the enumeration rather than its name. Clearly, it is not convenient for the user having to specify the type of the ephemeris based on how they are listed in the C++ code. Additionally, if the order of the values in the enumeration list changes in the future, converting 1 to EphemerisType may not result in direct_spice_ephemeris anymore. Thus, a custom implementation is needed, in which the JSON representation of an enumeration is the name of its possible values. For instance:

nlohmann::json j = "direct_spice_ephemeris";
EphemerisType ephemerisType = j;

Without the custom implementation, this leads to a run-time error.

Name-based implementation

Custom implementations for the to_json and from_json functions of all the supported enumerations are provided in the JSON Interface library. In this way, it is possible to convert nlohmann::json objects of value type string to enum and vice versa. However, the string representation for each enum value has to be manually provided. Although it is possible to replace the enumeration value names by identical strings during compile time, this was deemed too complex and, additionally, the enumeration names used in Tudat are not always optimal. For instance, consider this JSON file:

bodies.h
{
  "Earth": {
    "ephemeris": {
      "type": "tabulated_ephemeris"
    }
  }
}

In this case, the _ephemeris part is redundant. In Tudat this is necessary when other enumerations declared in the same namespace can share the same names (e.g. tabulated_atmosphere). However, in a JSON file, the string tabulated_ephemeris will only be used inside ephemeris objects of body objects, so the string tabulated is unambiguous. Thus, when defining the string representation for existing Tudat enumerations, the redundant information is removed. Additionally, the naming convention is to use lowerCamelCase strings.

The definition of the string representation of the enum values is done in a file in the JSON Interface directory but in the enumeration’s namespace (not in the json_interface namespace). This decision was made taking into account that these variables are only used inside the to_json and from_json functions of the enumeration, which must be declared in the enumeration’s namespace. A map is used to define the string representation of each enumeration type:

Tudat/JsonInterface/Environment/ephemeris.h
#include <Tudat/SimulationSetup/EnvironmentSetup/createEphemeris.h>
#include "Tudat/JsonInterface/Support/valueAccess.h"
#include "Tudat/JsonInterface/Support/valueConversions.h"

...

namespace tudat
{

namespace simulation_setup
{

...

//! Map of `EphemerisType`s string representations.
static std::map< EphemerisType, std::string > ephemerisTypes =
{
    { approximate_planet_positions, "approximatePlanetPositions" },
    { direct_spice_ephemeris, "directSpice" },
    { tabulated_ephemeris, "tabulated" },
    { interpolated_spice, "interpolatedSpice" },
    { constant_ephemeris, "constant" },
    { kepler_ephemeris, "kepler" },
    { custom_ephemeris, "custom" }
};

//! `EphemerisType` not supported by `json_interface`.
static std::vector< EphemerisType > unsupportedEphemerisTypes =
{
    custom_ephemeris
};

...

As you can see, the string representations are provided for all the enumeration values, even those that are not supported by the JSON interface. For instance, custom_ephemeris is not supported by the JSON interface, because a std::function cannot be provided using JSON files. Thus, this enum value is marked as unsupported by adding it to the variable unsupportedEphemerisTypes. In this way, when the user provides the value "custom", for the key ephemeris, rather than getting an IllevalValueError, an EphemerisType with value custom_ephemeris will be created without printing any warning. Then, when the actual EphemerisSettings are created, in its from_json function, the user will get an error in which it is said that custom ephemeris is not supported by the JSON interface but it does exist in Tudat, so if they want to use it they have to build their own custom JSON-based C++ application, in which the ephemeris function is defined manually (after reading the JSON input file containing the remainder of the settings).

Although a to_json and from_json function has to be written for each enumeration, they each are just a single line in which the functions stringFromEnum and enumFromString defined in Tudat/JsonInterface/Support/errorHandling.h are called:

Tudat/JsonInterface/Environment/ephemeris.h
#include <Tudat/SimulationSetup/EnvironmentSetup/createEphemeris.h>
#include "Tudat/JsonInterface/Support/valueAccess.h"
#include "Tudat/JsonInterface/Support/valueConversions.h"

...

namespace tudat
{

namespace simulation_setup
{

...

//! Convert `EphemerisType` to `nlohmann::json`.
inline void to_json( nlohmann::json& jsonObject, const EphemerisType& ephemerisType )
{
    jsonObject = json_interface::stringFromEnum( ephemerisType, ephemerisTypes );
}

//! Convert `nlohmann::json` to `EphemerisType`.
inline void from_json( const nlohmann::json& jsonObject, EphemerisType& ephemerisType )
{
    ephemerisType = json_interface::enumFromString( jsonObject, ephemerisTypes );
}

...

If the string representation of ephemerisType is not known, the recognised strings will be printed and an IllegalValueError will be thrown:

Unknown string "constatn" for enum tudat::simulation_setup::EphemerisType
Recognized strings:
  approximatePlanetPositions
  directSpice
  tabulated
  interpolatedSpice
  constant
  kepler
  custom
libc++abi.dylib: terminating with uncaught exception of type tudat::json_interface::IllegalValueError:
Illegal value for key: bodies.Earth.ephemeris.type
Could not convert value to expected type tudat::simulation_setup::EphemerisType

Note

All the files in which the to_json and from_json functions of enumerations and settings file are defined must include the files Tudat/JsonInterface/Support/valueAccess.h and Tudat/JsonInterface/Support/valueConversions.h. The former includes Tudat/JsonInterface/Support/errorHandling.h, exposing the functions json_interface::stringFromEnum and json_interface::enumFromString.

Caution

When converting an enumeration to or from a nlohmann::json object, the file in which its custom to_json and from_json functions are defined must be included (if the conversion takes place in a different file), or the functions have to be declared before being used if defined in the same file. If one forgets to include this file, the code will compile without giving any errors or warnings and the default implementation will be used, leading to a run-time error in which it is said that an int was expected when converting to the enumeration type.