Skip to content

Expose reading and setting of the ERFA leap second table#9329

Merged
mhvk merged 7 commits intoastropy:masterfrom
mhvk:erfa-expose-leap-second-table
Oct 25, 2019
Merged

Expose reading and setting of the ERFA leap second table#9329
mhvk merged 7 commits intoastropy:masterfrom
mhvk:erfa-expose-leap-second-table

Conversation

@mhvk
Copy link
Contributor

@mhvk mhvk commented Oct 6, 2019

@eteq - this is step 1 to using/updating the leap second table. I went a bit beyond @jwoillez's implementation by also allowing one to retrieve the current leap second table, using a new eraGetLeapSeconds. With that, it seemed it would be better to also rename eraUpdateLeapSeconds to eraSetLeapSeconds.

One can now retrieve these as structured arrays using _erfa.get_leap_second_table and _erfa.set_leap_second_table. EDIT (name change): erfa.get_leap_seconds and _erfa.set_leap_seconds.

EDIT 2: I hid the C functions behind a new leap_second singleton, with get, set, and update methods.

from astropy.utils.misc import check_broadcast

from . import ufunc
from .ufunc import (dt_eraASTROM, dt_eraLDBODY, dt_pv,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed imports here since they are not used anyway, and make much more sense in the _erfa module.

@mhvk
Copy link
Contributor Author

mhvk commented Oct 9, 2019

@eteq - one possible issue with the whole procedure is that the ERFA update is, per definition, global, ie., it will affect anything else that has the library in memory (sub-process, etc.). Similarly, the module is not sub-process safe. Of course, this is an issue only if different subprocesses had different wishes for the leap-second table, which is arguably very unlikely. I guess in principle it might be better instead to offer a hook in eraDat that calls out to get the leap-second table (which then by default returns the built-in one, but can be overridden somehow).

Anyway, my own sense is to punt on this for now, and leave this for 5.0 or so.

@mhvk
Copy link
Contributor Author

mhvk commented Oct 12, 2019

@eteq - yet another issue: if we go for an astropy-only approach on this for 4.0, it will not work for any distribution that separately ships liberfa, since in those we cannot actually override the leap second table. Since I work on such a distribution myself, this is a problem..

Or would it work for those to compile just dat.c ourselves?

@mhvk mhvk force-pushed the erfa-expose-leap-second-table branch from 07afb61 to 9e3055b Compare October 14, 2019 13:25
@mhvk
Copy link
Contributor Author

mhvk commented Oct 14, 2019

@eteq - would you be able to review? For now, I decided to forge ahead - we can worry about the distribution aspect later.

"""
Interfaces to the ERFA library, in particular for leap seconds.
"""
from datetime import datetime, timedelta
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It would be nice to use Time but that leads to round-trip issues when adding auto-updates upon loading of _erfa.

Copy link
Member

Choose a reason for hiding this comment

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

None of this is actually instantiated at import time, right? So can't we just use time imports inside the functions? I think that's a more consistent approach.

(If I missed an import-time requirement, then you can ignore this, though. It's not that bad since it's easy to transition between Time and datetime anyway)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wish!! (and tried). The problem is that Time initiation needs erfa installed (even the plainest formats like 'iso' rely on it). More specifically, Time.now() needs to have the leap seconds up to date (in case 'now' is on a leap-second day), yet here I need to have Time.now() accessible to tell whether to update the leap-second table.

Copy link
Member

@eteq eteq left a comment

Choose a reason for hiding this comment

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

I think this is overall the right direction, @mhvk. I think we don't need to worry a whole lot about the interface since nominally this is all "private" so we can iterate on it and not worry too much about backwards compatibility. So if #9365 + this is ready and we're bumping up against feature freeze, I'm ok with merging this without worrying a ton about some of these items and sort it out later.

But two broad issues to bring up in addition to my inline comments:

  1. Can the changes to dat go in a separate file? this would probably require adding it to erfaextras.h or something, but I think that will be easier to manage than the dat.c changes. My worry being this is a very substansive change and easy to screw up the "bring in a new erfa version process" if it's mixed in dat.c whereas if we did dat_extras.c or similar then we can just leave that file alone during the update process.
  2. The class level singleton seems a bit odd to me. Why use that approach instead of either a singleton instance (which I imagine is basically just a find-and-replace), vs standard module functions with globals as the shared state?

@@ -0,0 +1,177 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
Interfaces to the ERFA library, in particular for leap seconds.
Copy link
Member

Choose a reason for hiding this comment

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

I think it's better to make this only about leap seconeds. I.e., something like:

Suggested change
Interfaces to the ERFA library, in particular for leap seconds.
Interfaces to controlling leap seconds in the ERFA library.

and then change the name of the module file from interface.py to leap_seconds.py

"""
Interfaces to the ERFA library, in particular for leap seconds.
"""
from datetime import datetime, timedelta
Copy link
Member

Choose a reason for hiding this comment

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

None of this is actually instantiated at import time, right? So can't we just use time imports inside the functions? I think that's a more consistent approach.

(If I missed an import-time requirement, then you can ignore this, though. It's not that bad since it's easy to transition between Time and datetime anyway)

@mhvk
Copy link
Contributor Author

mhvk commented Oct 18, 2019

Yes, I've been wondering about making the changes to dat.c more minimal, ideally such that we can use a system library and things still work. But it needs me understand global variables a bit better...

On interfaces vs light_seconds for the module - I had the latter originally, but the problem is that the singleton also is called light_seconds, and from light_seconds import light_seconds then hides the module. Or am I wrong?

@mhvk
Copy link
Contributor Author

mhvk commented Oct 18, 2019

p.s. The reason for a singleton class is that I started with ScienceState - which is a singleton class. If you think it is clearer, I can certainly make it a singleton module, i.e., def get(), etc. That would also solve the naming issue...

@mhvk mhvk force-pushed the erfa-expose-leap-second-table branch from bf0fcd4 to 5dc7813 Compare October 18, 2019 20:17
@mhvk
Copy link
Contributor Author

mhvk commented Oct 18, 2019

@eteq - I managed to get out most of the changes to dat.c, into a new erfadatextra.c (prefixed by erfa in analogy with erfaversion.c - I think it makes sense to avoid calling anything not from SOFA by a name that could imply it came from SOFA).

I looked a bit at the module replacement for the current light_second singleton class, but don't like that then I cannot have properties. My sense would be to leave it as is for now, since it is so analogous to ScienceState (and we use singleton classes also in units.formats).

@mhvk
Copy link
Contributor Author

mhvk commented Oct 18, 2019

@eteq - from the commits, it may not be so clear, but the total change in dat.c is now quite small: relative to the commit just before that by you of @jwoillez's changes,

git diff ddab2476d477fa4e92b058c4c72e70f05535f31b cextern/erfa/dat.c
 #include "erfa.h"
+#include "erfadatextra.h"
 
 int eraDat(int iy, int im, int id, double fd, double *deltat)
 /*
@@ -144,10 +145,7 @@ int eraDat(int iy, int im, int id, double fd, double *deltat)
    enum { NERA1 = (int) (sizeof drift / sizeof (double) / 2) };
 
 /* Dates and Delta(AT)s */
-   static const struct {
-      int iyear, month;
-      double delat;
-   } changes[] = {
+   static const eraLEAPSECOND _changes[] = {
       { 1960,  1,  1.4178180 },
       { 1961,  1,  1.4228180 },
       { 1961,  8,  1.3728180 },
@@ -193,7 +191,13 @@ int eraDat(int iy, int im, int id, double fd, double *deltat)
    };
 
 /* Number of Delta(AT) changes */
-   enum { NDAT = (int) (sizeof changes / sizeof changes[0]) };
+   enum { _NDAT = (int) (sizeof _changes / sizeof _changes[0]) };
+
+/* Get/initialise leap-second if needed */
+   int NDAT;
+   eraLEAPSECOND *changes;
+
+   NDAT = eraDatini(_changes, _NDAT, &changes);
 
 /* Miscellaneous local variables */

@mhvk
Copy link
Contributor Author

mhvk commented Oct 24, 2019

@eteq - this is still the simplest PR of the two - could you re-review?

@eteq
Copy link
Member

eteq commented Oct 25, 2019

@mhvk - ok in its current state I think this is basically good. I didn't merge though because I wanted to give one more try of suggesting a change from the name interfaces.py to leap_seconds.py (which goes hand-in-hand with #9329 (comment)). I see your point from leap_seconds import leap_seconds is bad, but perhaps just have the file name be leap_second.py? Or change the class to leap_second_table or similar? I don't actually have a strong opinion on the exact wording as long as the module name be a bit more specific than "interfaces".

(But I approved because I think this is good other than that one name change.)

@mhvk
Copy link
Contributor Author

mhvk commented Oct 25, 2019

I'm quite happy to change the name, but not to leap_seconds! How about utils or helpers? In a way, the actual interface is in C so this is just a helper module/routine/class.

@eteq
Copy link
Member

eteq commented Oct 25, 2019

@mhvk - I'm ok with helpers, or possibly extras (since we're already using that on the erfa c side to mean "stuff beyond standard erfa bits"). If we do utils I'm 90% sure @adrn will just revert it all 😉

EDIT: after a moments more thought I decided I do prefer extras over helpers, but could live with either.

@pllim
Copy link
Member

pllim commented Oct 25, 2019

Why not leap first can make it utils anyway? 😏

@eteq
Copy link
Member

eteq commented Oct 25, 2019

@mhvk - also if it would be helpful I can do some of the clean-up steps to finish some of this and/or #9365 off if you're running out of time today.

@mhvk
Copy link
Contributor Author

mhvk commented Oct 25, 2019

@eteq - thanks, may be needed. But right now pushing up a version with helpers.py...

@mhvk mhvk force-pushed the erfa-expose-leap-second-table branch from 5dc7813 to 04a0e83 Compare October 25, 2019 22:10
@mhvk
Copy link
Contributor Author

mhvk commented Oct 25, 2019

coverage failure is a false positive. Since approved before, merging...

@mhvk mhvk merged commit b0a84f6 into astropy:master Oct 25, 2019
@mhvk mhvk deleted the erfa-expose-leap-second-table branch October 25, 2019 23:36
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.

3 participants