Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ported Tdbtt #14

Merged
merged 14 commits into from
Apr 3, 2018
Merged

Ported Tdbtt #14

merged 14 commits into from
Apr 3, 2018

Conversation

prakharcode
Copy link
Contributor

Ported eraTdbtt to pure Julia.
Ported dependency eraDtdb to pure Julia.

Used approximation due to precision error.

julia> ref = TDBEpoch(2013, 3, 18, 12)
2013-03-18T12:00:00.000 TDB

julia> ifu = AstronomicalTime.Epochs.dtdb(ref.jd1, ref.jd2, 0.0, 0.0, 0.0, 0.0)
0.0015985185850907002

julia> ou = ERFA.dtdb(ref.jd1, ref.jd2, 0.0, 0.0, 0.0, 0.0)
0.0015774019690913835

# Combine time argument (millennia) with deg/arcsec factor.
w = t / 3600.0
# Sun Mean Longitude.
elsun = mod(280.46645683 + 1296027711.03429 * w, 360.0) * DEG_2R
Copy link
Member

@giordano giordano Mar 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use deg2rad to convert degrees to radians and remove the DEG_2R constant. Here and below

test/runtests.jl Outdated
@test ref == TDBEpoch(TTEpoch("2013-03-18T11:59:59.998"))
@test ref == TDBEpoch(TCBEpoch("2013-03-18T12:00:17.718"))
@test ref == TDBEpoch(TCGEpoch("2013-03-18T12:00:00.795"))
@test UT1Epoch(ref) ≈ UT1Epoch("2013-03-18T11:58:52.994")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why approximated equality?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See below ;-)

@coveralls
Copy link

coveralls commented Mar 18, 2018

Coverage Status

Coverage increased (+0.5%) to 97.288% when pulling 8d7a9bf on prakharcode:tbdtt into 11eb598 on JuliaAstro:master.

@helgee
Copy link
Member

helgee commented Mar 18, 2018

The discrepancy seems too high to me.

end
# T**1
w1 = 0
for j in 679:-1:1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lower bound of this loop and of the following ones are wrong.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loops all work on non-overlapping segements of the coefficients (or would if they were correct 😉). Thus, the first step to simplify this would be to split coefficients into five separate arrays.

Copy link
Member

@giordano giordano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a side note, the fairhd array is quite big and is just a container of numbers. @helgee would be a StaticArray an option? I don't know how it'd perform in this case.

@codecov-io
Copy link

codecov-io commented Mar 18, 2018

Codecov Report

Merging #14 into master will increase coverage by 0.46%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #14      +/-   ##
==========================================
+ Coverage   96.82%   97.28%   +0.46%     
==========================================
  Files           6        6              
  Lines         252      295      +43     
==========================================
+ Hits          244      287      +43     
  Misses          8        8
Impacted Files Coverage Δ
src/AstronomicalTime.jl 100% <ø> (ø) ⬆️
src/conversions.jl 99.43% <100%> (+0.17%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 11eb598...8d7a9bf. Read the comment docs.

@helgee
Copy link
Member

helgee commented Mar 18, 2018

@giordano If I am not mistaken, a mutable array is better in this case since it will be heap-allocated and there are no copies when it is passed around.

w4 = 0
for j in 787:-1:1
w4 += fairhd[j][1] * sin(fairhd[j][2] * t + fairhd[j][3])
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there might be a more straight-forward way to compute this. These countdown-loops are bit confusing too me 😜

w4 += fairhd[j][1] * sin(fairhd[j][2] * t + fairhd[j][3])
end
# Multiply by powers of T and combine.
wf = t * (t * (t * (t * w4 + w3) + w2) + w1) + w0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. This is awful! Not your fault, of course @prakharcode .

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@evalpoly t w0 w1 w2 w3 w4

function dtdb(jd1, jd2, ut, elong, u, v)

t = ((jd1 - DJ00) + jd2) / DJM
# Convert UT to local solar time in radians.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation is off.

src/constants.jl Outdated

const DEG_2R = 1.745329251994329576923691e-2

const fairhd = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe move this to a separate file?

@helgee
Copy link
Member

helgee commented Mar 18, 2018

The array should probably not be accessed in row-major order.

test/runtests.jl Outdated
@@ -170,6 +172,9 @@ AstronomicalTime.update()
@test AstronomicalTime.Epochs.tttcg(julian2(tt), julian1(tt)) == ERFA.tttcg(julian2(tt), julian1(tt))
@test AstronomicalTime.Epochs.taiutc(julian1(tai), julian2(tai)) == ERFA.taiutc(julian1(tai), julian2(tai))
@test AstronomicalTime.Epochs.taiutc(julian2(tai), julian1(tai)) == ERFA.taiutc(julian2(tai), julian1(tai))
@test AstronomicalTime.Epochs.dtdb(julian1(tdb), julian2(tdb), 0.0, 0.0, 0.0, 0.0) ≈ ERFA.dtdb(julian1(tdb), julian2(tdb), 0.0, 0.0, 0.0, 0.0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

== ?

src/constants.jl Outdated
@@ -4,7 +4,7 @@ export MJD, J2000, J1950,
HOURS_PER_DAY, HOURS_PER_YEAR, HOURS_PER_CENTURY,
DAYS_PER_YEAR, DAYS_PER_CENTURY,
YEARS_PER_CENTURY,
OFFSET_TT_TAI, MOD_JD_77, ELG
OFFSET_TT_TAI, MOD_JD_77, ELG, fairhd, DJM, DJ00
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The coefficients should not be exported.

@helgee
Copy link
Member

helgee commented Mar 18, 2018

I really want to get rid of these funky backwards counting loops. Does anyone have a theory why the original code is written this way?
The coefficients could be split into five separate arrays in any case. The loops do not overlap.

@helgee helgee mentioned this pull request Mar 18, 2018
20 tasks
src/constants.jl Outdated
@@ -34,3 +34,8 @@ const OFFSET_TT_TAI = 32.184

const MOD_JD_77 = 43144.0
const ELG = 6.969290134e-10
const DJM = 365250.0
Copy link
Member

@giordano giordano Mar 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be defined in terms of DAYS_PER_YEAR? I guess these are days per millennium, maybe the constant can be given a more meaningful name.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@benelsen
Copy link

I assume the backward counting loops are for numerical stability in the summation, the coefficients are (mostly) decreasing in magnitude. The alternative is rewriting it to use a kbn summation or something alike.

@helgee
Copy link
Member

helgee commented Mar 18, 2018

Since Julia uses pairwise summation this might not be needed (see JuliaLang/julia#4039).

@giordano
Copy link
Member

But sum is not (yet?) used here

@helgee
Copy link
Member

helgee commented Mar 18, 2018

w0 = sum(fairhd0[:,1] .* sin(fairhd0[:,2] .* t .+ fairhd0[:,3]))

@giordano
Copy link
Member

Before merging this, we may want to check its performance. Currently it's slower than the ERFA function:

julia> tdb = TDBEpoch(2000, 1, 1, 12, 0, 0.0)
2000-01-01T12:00:00.000 TDB

julia> a = julian1(tdb)
2.4515445e6

julia> b = julian2(tdb)
0.5

julia> @benchmark ERFA.dtdb($a, $b, 0.0, 0.0, 0.0, 0.0)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     16.025 μs (0.00% GC)
  median time:      16.182 μs (0.00% GC)
  mean time:        16.353 μs (0.00% GC)
  maximum time:     46.679 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1

julia> @benchmark AstronomicalTime.Epochs.dtdb($a, $b, 0.0, 0.0, 0.0, 0.0)
BenchmarkTools.Trial: 
  memory estimate:  37.05 KiB
  allocs estimate:  2371
  --------------
  minimum time:     36.573 μs (0.00% GC)
  median time:      37.647 μs (0.00% GC)
  mean time:        40.081 μs (3.31% GC)
  maximum time:     974.220 μs (89.31% GC)
  --------------
  samples:          10000
  evals/sample:     1

# =====================

# T**0
w0 = 0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

w0w4 are first assigned an Int64 (w0 = 0) and then reassigned a Float64 (w0 += ) two lines later.
Using w0 = 0.0 (same for w1w4) one can prevent a costly type-instability.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! 10% of improvement is very nice

@benelsen
Copy link

This is due to a type-instability in the variables used for summation, once those are initialized as floats the allocations are gone and the performance is better.

julia> b1 = @benchmark AstronomicalTime.Epochs.dtdb($a, $b, 0.123, 0.234, 0.456, 0.789)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     10.916 μs (0.00% GC)
  median time:      11.054 μs (0.00% GC)
  mean time:        11.063 μs (0.00% GC)
  maximum time:     27.996 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1

julia> b2 = @benchmark ERFA.dtdb($a, $b, 0.123, 0.234, 0.456, 0.789)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     12.242 μs (0.00% GC)
  median time:      12.295 μs (0.00% GC)
  mean time:        12.302 μs (0.00% GC)
  maximum time:     21.806 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1

julia> judge(median(b1), median(b2))
BenchmarkTools.TrialJudgement: 
  time:   -10.09% => improvement (5.00% tolerance)
  memory: +0.00% => invariant (1.00% tolerance)

w4 += fairhd[j][1] * sin(fairhd[j][2] * t + fairhd[j][3])
end
# Multiply by powers of T and combine.
wf = t * (t * (t * (t * w4 + w3) + w2) + w1) + w0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already wrote this before but got lost: replace this line with

wf = @evalpoly t w0 w1 w2 w3 w4

@helgee
Copy link
Member

helgee commented Mar 31, 2018

I did a few experiments. The fastest version I was able to come up with is very close to ERFA in terms of performance (consistently within 10%). It uses StaticArrays.jl after all. Originally I did not realize that the algorithm does not work on a matrix of coefficients but actually iterates over an array of small vectors.
With the coefficients partioned in five tuples of SVectors the loops can be written like this:

    w0 = 0.0
    for j in reverse(eachindex(fairhd0))
        w0 += fairhd0[j][1] * sin(fairhd0[j][2] * t + fairhd0[j][3])
    end

Which is much nicer IMHO.

@giordano
Copy link
Member

The vector themselves can be written in the reverse order (or reversed at compile time), rather than using reverse at runtime.

@helgee
Copy link
Member

helgee commented Mar 31, 2018

Does not make a significant difference.

@helgee
Copy link
Member

helgee commented Mar 31, 2018

Final version, with tuples of tuples instead of StaticArrays.jl:

function dtdb_tuples2(jd1, jd2, ut, elong, u, v)
    t = ((jd1 - DJ00) + jd2) / DJM
    # Convert UT to local solar time in radians.
    tsol = mod(ut, 1.0) * 2π  + elong

    # FUNDAMENTAL ARGUMENTS:  Simon et al. 1994.
    # Combine time argument (millennia) with deg/arcsec factor.
    w = t / 3600.0
    # Sun Mean Longitude.
    elsun = deg2rad(mod(280.46645683 + 1296027711.03429 * w, 360.0))
    # Sun Mean Anomaly.
    emsun = deg2rad(mod(357.52910918 + 1295965810.481 * w, 360.0))
    # Mean Elongation of Moon from Sun.
    d = deg2rad(mod(297.85019547 + 16029616012.090 * w, 360.0))
    # Mean Longitude of Jupiter.
    elj = deg2rad(mod(34.35151874 + 109306899.89453 * w, 360.0))
    # Mean Longitude of Saturn.
    els = deg2rad(mod(50.07744430 + 44046398.47038 * w, 360.0))
    # TOPOCENTRIC TERMS:  Moyer 1981 and Murray 1983.
    wt =   +  0.00029e-10 * u * sin(tsol + elsun - els)
    +  0.00100e-10 * u * sin(tsol - 2.0 * emsun)
    +  0.00133e-10 * u * sin(tsol - d)
    +  0.00133e-10 * u * sin(tsol + elsun - elj)
    -  0.00229e-10 * u * sin(tsol + 2.0 * elsun + emsun)
    -  0.02200e-10 * v * cos(elsun + emsun)
    +  0.05312e-10 * u * sin(tsol - emsun)
    -  0.13677e-10 * u * sin(tsol + 2.0 * elsun)
    -  1.31840e-10 * v * cos(elsun)
    +  3.17679e-10 * u * sin(tsol)
    # =====================
    # Fairhead et al. model
    # =====================

    # T**0
    w0 = 0.0
    for j in eachindex(fairhd0_4)
        @muladd w0 += fairhd0_4[j][1] * sin(fairhd0_4[j][2] * t + fairhd0_4[j][3])
    end
    # T**1
    w1 = 0.0
    for j in eachindex(fairhd1_4)
        @muladd w1 += fairhd1_4[j][1] * sin(fairhd1_4[j][2] * t + fairhd1_4[j][3])
    end
    # T**2
    w2 = 0.0
    for j in eachindex(fairhd2_4)
        @muladd w2 += fairhd2_4[j][1] * sin(fairhd2_4[j][2] * t + fairhd2_4[j][3])
    end
    # T**3
    w3 = 0.0
    for j in eachindex(fairhd3_4)
        @muladd w3 += fairhd3_4[j][1] * sin(fairhd3_4[j][2] * t + fairhd3_4[j][3])
    end
    # T**4
    w4 = 0.0
    for j in eachindex(fairhd4_4)
        @muladd w4 += fairhd4_4[j][1] * sin(fairhd4_4[j][2] * t + fairhd4_4[j][3])
    end
    # Multiply by powers of T and combine.
    #= wf = t * (t * (t * (t * w4 + w3) + w2) + w1) + w0 =#
    wf = @evalpoly t w0 w1 w2 w3 w4
    # Adjustments to use JPL planetary masses instead of IAU.
    wj = 0.00065e-6 * sin(6069.776754 * t + 4.021194) +
        0.00033e-6 * sin( 213.299095 * t + 5.543132) +
        (-0.00196e-6 * sin(6208.294251 * t + 5.696701)) +
        (-0.00173e-6 * sin(  74.781599 * t + 2.435900)) +
        0.03638e-6 * t * t
    # ============
    # Final result
    # ============
    # TDB-TT in seconds.
    w = wt + wf + wj
end

0.6: 10.504 μs
0.7: 7.166 μs 🎉

@giordano
Copy link
Member

Good! I'm surprised long tuples (more than 16 elements) don't have some overhead. What's performance of the function from ERFA on your machine?

@helgee
Copy link
Member

helgee commented Mar 31, 2018 via email

@benelsen
Copy link

AFAIR StaticArrays are basically NTuples with added sugar.
You can maybe try sprinkling in some @inbounds for the last few ‰ as it's not really possible to get out of bounds there.

@prakharcode
Copy link
Contributor Author

@helgee is this error tolerable: -9.930719894379461e-5 == -9.930719894379447e-5, when comparing AstronomicalTime.Epochs.dtdb(julian1(tdb), julian2(tdb), 0.0, 0.0, 0.0, 0.0) == ERFA.dtdb(julian1(tdb), julian2(tdb), 0.0, 0.0, 0.0, 0.0)
?

@prakharcode
Copy link
Contributor Author

@giordano the edits suggested by you, @helgee and @benelsen helped a lot in making the function efficient. Now the function benchmarks as follows:

julia> b1 = @benchmark ERFA.dtdb($a, $b, 0.0, 0.0, 0.0, 0.0)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     16.120 μs (0.00% GC)
  median time:      16.244 μs (0.00% GC)
  mean time:        16.277 μs (0.00% GC)
  maximum time:     36.475 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1

julia> b2 = @benchmark AstronomicalTime.Epochs.dtdb($a, $b, 0.0, 0.0, 0.0, 0.0)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     11.470 μs (0.00% GC)
  median time:      11.766 μs (0.00% GC)
  mean time:        11.866 μs (0.00% GC)
  maximum time:     23.690 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1

julia> judge(median(b2), median(b1))
BenchmarkTools.TrialJudgement: 
  time:   -27.57% => improvement (5.00% tolerance)
  memory: +0.00% => invariant (1.00% tolerance)

@helgee
Copy link
Member

helgee commented Apr 3, 2018

There is no error on my machine. Did you change the direction of the loops?

@prakharcode
Copy link
Contributor Author

Yes @helgee, I resolved it!

src/constants.jl Outdated
@@ -34,3 +34,8 @@ const OFFSET_TT_TAI = 32.184

const MOD_JD_77 = 43144.0
const ELG = 6.969290134e-10
const DAYS_PER_MILLENNIUM = 365250.0
const DJ00 = 2451545.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as J2000

@helgee
Copy link
Member

helgee commented Apr 3, 2018

You need to add the new dependency to REQUIRE.

@helgee helgee merged commit fe85130 into JuliaAstro:master Apr 3, 2018
@helgee
Copy link
Member

helgee commented Apr 3, 2018

Nice work everybody 🎉

@prakharcode prakharcode deleted the tbdtt branch April 3, 2018 17:33
helgee pushed a commit that referenced this pull request Apr 4, 2018
helgee added a commit that referenced this pull request Apr 4, 2018
)

* Use legal generated functions for conversions

* Enable precompilation

* Minor refactor of Period type and whitespace cleanup

* Revert "Minor refactor of Period type and whitespace cleanup"

This reverts commit b375073.

* Make parameter for transform macro explicit

* Replacing the calls to ERFA by pure Julia functions for eraTaiutc (#13)

* Replacing the calls to ERFA by pure Julia functions for eraUtctai (#15)

* Ported Tdbtt (#14)

* Use legal generated functions for conversions

* Allow additional arguments in transform macro

* Fix transform and various cleanups

* Cleanup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants