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.
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) = 0
offset_tdb2et (generic function with 1 method)
julia> offset_et2tdb(sec::Number) = 0
offset_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.nodes
1-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.0
1.0
julia> offset_dts2ntsa(sec::Number) = OFFSET_DTS_TO_NTSA
offset_dts2ntsa (generic function with 1 method)
julia> offset_ntsa2dts(sec::Number) = -OFFSET_DTS_TO_NTSA
offset_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.nodes
2-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.0
offset_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.nodes
3-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*86400
2000-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.