Creating Custom Timescales

In Tempo.jl, all timescales connections and epoch conversions are handled through a directed graph. A default graph (TIMESCALES), containing a set of predefined timescales is provided by this package. However, this package also provided a set of routines to either extend such graph or create a completely custom one. In this tutorial, we will explore both alternatives.

Defining a New Timescale

Custom timescales can be created with the @timescale macro, which automatically creates the required types and structures, given the timescale acronym, an integer ID and, eventually, the full name of the timescale.

julia> @timescale ET 15 EphemerisTime

The ID is an integer that is internally used to uniquely represent the timescale, whereas the acronym is used to alias such ID. It is also possible to define multiple acronyms associated to the same ID but you cannot assign multiple IDs to the same acronym. In case a full name is not provided, a default one will be built by appending TimeScale to the acronym.

Warning

The IDs from 1 to 10 are used to define the standard timescales of the package. To avoid unexpected behaviors, custom timescales should be registered with higher IDs.

In the previous example, we have created a custom timescale named EphemerisTime, with ID 15. We are now able to define epochs with respect to ET, but we cannot perform conversions towards other timescales until we register it in a graph system:

julia> ep = Epoch(20.425, ET)2000-01-01T12:00:20.4249 ET
julia> convert(TT, ep)ERROR: EpochConversionError: cannot convert Epoch from the timescale Main.EphemerisTime to TerrestrialTime.

Extending the Default Graph

In this section, the goal is to register ET as a zero-offset scale with respect to TDB. To register this timescale in the default graph, we first need to define the offset functions of ET with respect to TDB:

julia> offset_tdb2et(sec::Number) = 0offset_tdb2et (generic function with 1 method)
julia> offset_et2tdb(sec::Number) = 0offset_et2tdb (generic function with 1 method)

Since we have assumed that the two scales are identical, our functions will always return a zero offset. Rememeber that timescales graph is directed, meaning that if the user desires to go back and forth between two timescales, both transformations must be defined. The input argument of such functions is always the number of seconds since J2000 expressed in the origin timescale.

Finally, the add_timescale! method can be used to register ET within the default graph:

julia> add_timescale!(TIMESCALES, ET, offset_tdb2et, parent=TDB, ftp=offset_et2tdb)

If the inverse transformation (from ET to TDB) is not provided, only one-way epoch conversions will be allowed. We can now check that the desired timescale has been properly registered and performs the same as TDB:

julia> ep = Epoch("200.432 TT")2000-07-19T22:22:04.8000 TT
julia> convert(TDB, ep)2000-07-19T22:22:04.7995 TDB
julia> convert(ET, ep)2000-07-19T22:22:04.7995 ET

Creating a Custom Graph

To create a custom directed graph to handle timescales, Tempo.jl provides the TimeSystem type. Therefore, let us define a new time transformation system called TIMETRANSF:

julia> const TIMETRANSF = TimeSystem{Float64}()TimeSystem{Float64}(SMDGraphs.MappedNodeGraph{Tempo.TimeScaleNode{Float64}, Graphs.SimpleGraphs.SimpleDiGraph{Int64}}(Graphs.SimpleGraphs.SimpleDiGraph{Int64}(0, Vector{Int64}[], Vector{Int64}[]), Dict{Int64, Int64}(), Tempo.TimeScaleNode{Float64}[], Dict{Int64, Dict{Int64, Vector{Int64}}}(), Dict{Int64, Dict{Int64, Int64}}()))

This object contains a graph and the properties associated to the new time-system defined in TIMETRANSF. At the moment, the computational graph is empty and we need to manually populate it with the new transformations.

We begin by creating a new timescale:

julia> @timescale DTS 1 DefaultTimeScale

Once created, the new timescale is ready to be registered. If it is the first scale registered in the computational graph, nothing else than the type alias is needed and the registration can be performed as follows:

julia> add_timescale!(TIMETRANSF, DTS)
julia> TIMETRANSF.scales.nodes1-element Vector{Tempo.TimeScaleNode{Float64}}: TimeScaleNode{Float64}(name=DTS, id=1)

Instead, in case the timescale is linked to a parent one, offset functions shall be defined. In this example, assume we want to register the timescales NTSA and NTSB such that NTSA has DTS as parent and a constant offset of 1 second, whereasNTSB has NTSA as parent and a linear offset with slope of 1/86400.

We begin by creating the first timescale:

julia> @timescale NTSA 2 NewTimeScaleA

We then define its offset functions and register it in TIMETRANSF via the add_timescale! method:

julia> const OFFSET_DTS_TO_NTSA = 1.01.0
julia> offset_dts2ntsa(sec::Number) = OFFSET_DTS_TO_NTSAoffset_dts2ntsa (generic function with 1 method)
julia> offset_ntsa2dts(sec::Number) = -OFFSET_DTS_TO_NTSAoffset_ntsa2dts (generic function with 1 method)
julia> add_timescale!(TIMETRANSF, NTSA, offset_dts2ntsa, parent=DTS, ftp=offset_ntsa2dts)

Now, if we have a look to the computational graph, we'll see that NTSA is registered:

julia> TIMETRANSF.scales.nodes2-element Vector{Tempo.TimeScaleNode{Float64}}:
 TimeScaleNode{Float64}(name=DTS, id=1)

 TimeScaleNode{Float64}(name=NTSA, id=2, parent=1)

If now we create a DTS epoch, we can leverage our custom time transformation system to convert it to an epoch in the NTSA timescale:

julia> e = Epoch(0.0, DTS)2000-01-01T12:00:00.0000 DTS
julia> convert(NTSA, e, system=TIMETRANSF)2000-01-01T12:00:01.0000 NTSA

Whenever the conversions are based on a custom time system, the graph must be provided as an additional argument to the convert method.

To conclude the example, we will now add the NTSB scale but only register the NTSA -> NTSB transformation:

julia> @timescale NTSB 3 NewTimeScaleB
julia> offset_ntsa2ntsb(sec::Number) = sec/86400.0offset_ntsa2ntsb (generic function with 1 method)
julia> add_timescale!(TIMETRANSF, NTSB, offset_ntsa2ntsb, parent=NTSA)

Now, let's have a look to the nodes in the graph:

julia> TIMETRANSF.scales.nodes3-element Vector{Tempo.TimeScaleNode{Float64}}:
 TimeScaleNode{Float64}(name=DTS, id=1)

 TimeScaleNode{Float64}(name=NTSA, id=2, parent=1)

 TimeScaleNode{Float64}(name=NTSB, id=3, parent=2)

You can see that the new timescale has been registered with the desired integer ID 3. To test the complete system, we will translate forwad of 2 days the previous epoch e and transform it in both timescales:

julia> e += 2*864002000-01-03T12:00:00.0000 DTS
julia> ea = convert(NTSA, e, system=TIMETRANSF)2000-01-03T12:00:01.0000 NTSA
julia> eb = convert(NTSB, e, system=TIMETRANSF)2000-01-03T12:00:03.0000 NTSB

As expected, we obtain translations of 1 and 3 seconds, respectively.