Skip to content

Conversation

@eteq
Copy link
Member

@eteq eteq commented Nov 9, 2015

This was inspired by, subsumes, and closes #4290 (and it includes its commit history).

There, @StuartLittlefair pointed out (and fixed) the fact that the intermediate rotation transforms often expect tt, and most obstimes and such are given as utc. This PR extends that further to the whole coordinates module where erfa is used, doing a transformation to whatever the appropriate scales when needed.

Note that in many cases (perhaps all here?) this is a small effect - e.g. the TT vs. TDB difference is unimportant for most coordinates, and just about all of these scale shifts are in the machine noise for secular things like the location of the Earth relative to the Sun (except maybe for some corner cases that I'm not aware of). But this is definitely more "right" than it was before.

Two questions about this, though:

  • @mhvk, I think you're probably best equipped to answer this: do you see anywhere here that a UT1-UTC operation might need additional information not already contained in the Time object? (I think this is a non-issue, but I just wanted to check to be sure...)
  • There's at least one place where UT1-UTC is now needed when it wasn't before. So this means users will get more of the DUT1 warnings. I don't think this is really an issue (because it's a real problem, so it should be warned about), but it's something to at least consider.

@eteq eteq added this to the v1.1.0 milestone Nov 9, 2015
Copy link
Contributor

Choose a reason for hiding this comment

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

@taldcroft - I have on and off wrote sequences like this as well, as it helps speed up the case where the scale is already correct (avoiding the creation of a new instance). But it is also a bit ugly... Often, like here, it is in preparation to get both jd, so maybe a function to get those in a certain scale? (with default, just returning internal jd1, jd2 if no scale given). But perhaps this is overoptimization...

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, @eteq - since this seem to happen mostly when you are getting out jd1 and jd2, maybe this would be something for a very small helper function instead:

def jds(time, scale=None):
    t = time if scale is None or scale == time.scale else getattr(time, scale)
    return t.jd1, t.jd2

which allows you do do:

jd1, jd2 = jds(obstime, scale='utc')

(or for simple functions to put in directly as arguments: erfa.obl06(*jds(obstime, 'tt')))

Copy link
Member

Choose a reason for hiding this comment

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

@mhvk - relevant to the above questions, here is the code:

    def __getattr__(self, attr):
        """
        Get dynamic attributes to output format or do timescale conversion.
        """
        if attr in self.SCALES and self.scale is not None:
            tm = self.replicate()
            tm._set_scale(attr)
            return tm

What about:

  • Return self if attr == self.scale?
  • Cache other scales so getting both jd1 and jd2 after transforming will be efficient?
    Roughly:
    def __getattr__(self, attr):
        """
        Get dynamic attributes to output format or do timescale conversion.
        """
        if attr in self.SCALES and self.scale is not None:
            if attr == self.scale:
                tm = self
            elif attr in self._scale_cache:
                tm = self._scale_cache[attr]
            else:
                tm = self.replicate()
                tm._set_scale(attr)
                self._scale_cache[attr] = tm
            return tm

Copy link
Member

Choose a reason for hiding this comment

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

I guess we would need to clear the cache if delta_ut1_utc or delta_tdb_tt were set.

Copy link
Contributor

Choose a reason for hiding this comment

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

@taldcroft - since Time instances are immutable, I think we could at least do the very simple change of just returning self if the scale is already correct. The caching I'm less sure about; maybe adding a bit too much complexity.

Copy link
Member Author

Choose a reason for hiding this comment

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

I like @taldcroft's suggestion here. The first thing I thought was "I should be able to do time.tt.jd1, time.tt.jd2, because that's the clearest. But then I checked and realized it's not cached, so I decided it wasn't fast enough given that this is in inner loops. So I naturally assumed there would be a cache and was surprised that there wasn't.

If it helps any, there's precedence in coordinates itself for this. So I'd advocate for instead doing just as @taldcroft says here.

I think that's a "new feature", though, so it probably has to wait for 1.2 . In the meantime, I will create a utility function as @mhvk suggests just for coordinates, which we can later change to just being return getattr(time, scale).jd1, getattr(time, scale).jd2 or just replace all of it with direct conversions once the caching and such is in.

Copy link
Member

Choose a reason for hiding this comment

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

@eteq @mhvk - I've created issue #4309 to discuss this since it's getting a bit OT here.

@mhvk
Copy link
Contributor

mhvk commented Nov 10, 2015

Great! Some minor comments inline.

@eteq eteq changed the title Safer use if jd1/jd2 times when using erfa Safer use of jd1/jd2 times when using erfa Nov 13, 2015
@eteq
Copy link
Member Author

eteq commented Nov 13, 2015

Ok, this is now using a get_jd12 function to wrap up all the jd1/jd2 getting. Turns out a fair number of places *get_jd12(...) doesn't work because the jd1/jd2 are passed into the middle of some long erfa function. But I think this is still clearer/safer, particularly for the UT1<->other scales case. For 1.2 (or whenever #4309 gets implemented and in), that can be trivially changed to use the caching mechanism there. But if it makes sense in the meantime, @mhvk, this get_jd12 could be moved into time and used there as well. I'd rather get this in first and have that as a separate PR, though, so that we don't have to worry about it blocking 1.1...

Copy link
Contributor

Choose a reason for hiding this comment

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

By just accessing delta_ut1_utc you'll get an IndexError if the time is outside of the IERS table. I think if the time is OK, it is not a problem for the time instance to change, but if it is outside IERS, it should ideally not be set. I think you would need to do something like:

    if scale == time.scale:
        newtime = time
    else:
        try:
            newtime = getattr(time, scale)
        except IndexError:
            # similar warning to that in get_dut1dutc
            newtime = time
    return newtime.jd1, newtime.jd2

Copy link
Member Author

Choose a reason for hiding this comment

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

ohh, I didn't realize the accessor triggered the error - will do what you suggest here, then.

@mhvk
Copy link
Contributor

mhvk commented Nov 13, 2015

@eteq - the code does feel quite a bit cleaner with the little helper function! I also think it all looks good, though I cannot say I've looked through the whole code to see whether it is complete.

@mhvk
Copy link
Contributor

mhvk commented Nov 13, 2015

Replying to your comment: the only way to do the *get_jd1(...) in a longer function would be to use keyword arguments afterwards. Maybe worth it for the case where there is only one further argument; for the rest, it probably just decreases readability (so, fine to keep as is).

On moving to time, I agree with not doing that now/here; it should be quite trivial to change things around if/when Time gets a cache and/or a jd1_jd2 or jd12 property, but in the meantime this will work.

@eteq
Copy link
Member Author

eteq commented Nov 17, 2015

Alright, I fixed the issue @mhvk mentioned and in the process decided to convert the IndexError IERS gives out to a more specialized (catchable) IERSRangeError. Still basically backwards compatible because it's a subclass of IndexError, so code that caught IndexError before still works the same. Also added a test to make sure the issue @mhvk mentioned gets caught in it ever re-appears.

This should now be all set, so if the tests pass I'll go ahead and merge and backport it.

eteq added a commit that referenced this pull request Nov 17, 2015
Safer use of jd1/jd2 times when using erfa
@eteq eteq merged commit 96611a3 into astropy:master Nov 17, 2015
@eteq eteq deleted the safer-times-in-coords branch November 17, 2015 13:27
@mhvk
Copy link
Contributor

mhvk commented Nov 17, 2015

@eteq - good to see this in, and good idea to have the IERSRangeError.

eteq added a commit to eteq/astropy that referenced this pull request Nov 17, 2015
Safer use of jd1/jd2 times when using erfa
@eteq
Copy link
Member Author

eteq commented Nov 17, 2015

backported to v1.1.x in db623a2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants