Skip to content

Commit

Permalink
Merge pull request #26 from JuliaSpaceMissionDesign/25-implement-dura…
Browse files Browse the repository at this point in the history
…tion-as-a-subtype-of-number

25 implement duration as a subtype of number
  • Loading branch information
MicheleCeresoli authored Aug 1, 2024
2 parents 75abe71 + 5d63f21 commit d51e487
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 52 deletions.
81 changes: 48 additions & 33 deletions src/duration.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

"""
Duration{T}
Duration{T} <: Number
A `Duration` represents a period of time, split into an integer number of seconds and a
fractional part.
Expand All @@ -9,54 +9,44 @@ fractional part.
- `seconds`: The integer number of seconds.
- `fraction`: The fractional part of the duration, where `T` is a subtype of `Number`.
"""
struct Duration{T}
struct Duration{T} <: Number
seconds::Int
fraction::T
end

function Duration(seconds::T) where {T<:Number}
i, f = divrem(seconds, 1)
return Duration{T}(i, f)
function Duration{T}(seconds::Number) where {T <: Number}
i,f = divrem(seconds, 1)
return Duration{T}(convert(Int, i), T(f))
end

function Duration(sec::Int, frac::T) where {T<:Number}
return Duration{T}(sec, frac)
end
function Duration(seconds::T) where {T <: Number}
Duration{T}(seconds)
end

ftype(::Duration{T}) where T = T
value(d::Duration{T}) where T = d.seconds + d.fraction

function Base.isless(d::Duration{T}, q::Number) where T
return value(d) < q
end
# ---
# Type Conversions and Promotions

function Base.isless(d::Duration{T1}, d2::Duration{T2}) where {T1, T2}
return value(d) < value(d2)
function Base.convert(::Type{Duration{T}}, d::Duration{S}) where {T,S}
return Duration(d.seconds, convert(T, d.fraction))
end

function fmasf(a, b, mul)
amulb = fma(mul, a, b)
i, f = divrem(amulb, 1)
return i, f
function Base.convert(::Type{T}, d::Duration{S}) where {T<:Number,S}
return Duration(d.seconds, convert(T, d.fraction))
end

function Base.:-(d1::Duration, d2::Duration)
s1, f1 = d1.seconds, d1.fraction
s2, f2 = d2.seconds, d2.fraction
ds, df = divrem(f1 - f2, 1)
sec = s1 - s2 + ds
if df < 0
sec -= 1
df += 1
end
return Duration(convert(Int, sec), df)
function Base.promote_rule(::Type{Duration{T}}, ::Type{Duration{S}}) where {T,S}
return promote_rule(T, S)
end

function Base.:+(d1::Duration, d2::Duration)
s1, f1 = d1.seconds, d1.fraction
s2, f2 = d2.seconds, d2.fraction
s, f = fmasf(f1, f2, 1)
return Duration(convert(Int, s1 + s2 + s), f)
end
# ----
# Operations

Base.isless(d::Duration, q::Number) = value(d) < q
Base.isless(q::Number, d::Duration) = q < value(d)
Base.isless(d1::Duration, d2::Duration) = value(d1) < value(d2)

function Base.:+(d::Duration, x::Number)
es, ef = d.seconds, d.fraction
Expand All @@ -65,6 +55,13 @@ function Base.:+(d::Duration, x::Number)
return Duration(convert(Int, es + xs + s), f)
end

function Base.:+(d1::Duration, d2::Duration)
s1, f1 = d1.seconds, d1.fraction
s2, f2 = d2.seconds, d2.fraction
s, f = fmasf(f1, f2, 1)
return Duration(convert(Int, s1 + s2 + s), f)
end

function Base.:-(d::Duration, x::Number)
es, ef = d.seconds, d.fraction
xs, xf = divrem(x, 1)
Expand All @@ -77,3 +74,21 @@ function Base.:-(d::Duration, x::Number)
return Duration(convert(Int, sec), df)
end

function Base.:-(d1::Duration, d2::Duration)
s1, f1 = d1.seconds, d1.fraction
s2, f2 = d2.seconds, d2.fraction
ds, df = divrem(f1 - f2, 1)
sec = s1 - s2 + ds
if df < 0
sec -= 1
df += 1
end
return Duration(convert(Int, sec), df)
end


function fmasf(a, b, mul)
amulb = fma(mul, a, b)
i, f = divrem(amulb, 1)
return i, f
end
28 changes: 9 additions & 19 deletions src/epoch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ struct Epoch{S,T}
end

function Epoch{S}(seconds::Number) where {S<:AbstractTimeScale}
sec, frac = divrem(seconds, 1)
return Epoch{S, typeof(frac)}(S(), Duration(Int(sec), frac))
d = Duration(seconds)
return Epoch{S, ftype(d)}(S(), d)
end

Epoch(sec::Number, ::S) where {S<:AbstractTimeScale} = Epoch{S}(sec)
Expand All @@ -83,15 +83,15 @@ Epoch(dt::DateTime, ::Type{S}) where {S<:AbstractTimeScale} = Epoch{S}(j2000s(dt
Epoch(e::Epoch) = e

Epoch{S,T}(e::Epoch{S,T}) where {S,T} = e
Epoch{S,T}(e::Epoch{S,N}) where {S, N, T} = Epoch{S}(T(j2000s(e)))
Epoch{S,T}(e::Epoch{S,N}) where {S, N, T} = Epoch{S,T}(e.scale, convert(T, e.dur))

# Construct an epoch from an ISO string and a scale
function Epoch(s::AbstractString, scale::S) where {S <: AbstractTimeScale}
y, m, d, H, M, sec, sf = parse_iso(s)

# TODO: the precision of this could be improved
_, jd2 = calhms2jd(y, m, d, H, M, sec + sf)
return Epoch(jd2 * 86400, scale)
return Epoch(jd2 * DAY2SEC, scale)
end

# Construct an epoch from an ISO string
Expand Down Expand Up @@ -201,21 +201,8 @@ function Base.:-(::Epoch{S1}, ::Epoch{S2}) where {S1, S2}
throw(ErrorException("only epochs defined in the same timescale can be subtracted."))
end

function Base.:+(e::Epoch{S, N}, x::Number) where {S, N}
return Epoch{S, N}(timescale(e), e.dur + x)
end

function Base.:-(e::Epoch{S, N}, x::Number) where {S, N}
return Epoch{S, N}(timescale(e), e.dur - x)
end

function Base.:+(e::Epoch{S, N}, d::Duration{<:Number}) where {S, N}
return Epoch{S, N}(timescale(e), e.dur + d)
end

function Base.:-(e::Epoch{S, N}, d::Duration{<:Number}) where {S, N}
return Epoch{S, N}(timescale(e), e.dur - d)
end
Base.:+(e::Epoch, x::Number) = Epoch(timescale(e), e.dur + x)
Base.:-(e::Epoch, x::Number) = Epoch(timescale(e), e.dur - x)

function (::Base.Colon)(start::Epoch, step::Number, stop::Epoch)
step = start < stop ? step : -step
Expand All @@ -227,6 +214,9 @@ function (::Base.Colon)(start::Epoch, step::Duration, stop::Epoch)
return (:)(start, value(step), stop)
end

(::Base.Colon)(start::Epoch, stop::Epoch) = (:)(start, 86400, stop)


Base.isless(e1::Epoch{S}, e2::Epoch{S}) where {S} = e1.dur < e2.dur

function Base.isapprox(e1::Epoch{S}, e2::Epoch{S}; kwargs...) where {S}
Expand Down
17 changes: 17 additions & 0 deletions test/Tempo/duration.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

# TestSet for Duration
@testset "Duration" begin

# Test Duration constructor with a floating point number
d1 = Duration(5.75)
@test d1.seconds == 5
Expand All @@ -11,10 +12,25 @@
@test d2.seconds == 10
@test d2.fraction == 0.25

# Check Constructor with a Big Float
db1 = Duration(BigFloat(2.5422))

@test db1.seconds == 2
@test db1.fraction 0.5422 atol=1e-14 rtol=1e-14

# Test duration type
@test Tempo.ftype(db1) == BigFloat

# Test value function
@test value(d1) == 5.75
@test value(d2) == 10.25

# Test duration conversion
db2 = convert(BigFloat, d1)
@test Tempo.ftype(db2) == BigFloat
@test db2.seconds == 5
@test db2.fraction 0.75 atol=1e-14 rtol=1e-14

# Test isless with a number
@test d1 < 6.0
@test d2 < 10.5
Expand Down Expand Up @@ -63,4 +79,5 @@
@test d10.fraction == d1.fraction
@test d11.seconds == d1.seconds
@test d11.fraction == d1.fraction

end
2 changes: 2 additions & 0 deletions test/Tempo/epoch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@
@test_throws ErrorException e3-e1

ems = e1:86400:e2
ems2 = e1:e2
for j = 2:lastindex(ems)
@test ems[j] == e1 + 86400*(j-1)
@test ems2[j] == e1 + 86400*(j-1)
end

# Based on Vallado "Fundamental of astrodynamics" page 196
Expand Down

0 comments on commit d51e487

Please sign in to comment.