Skip to content

A reorganization of timekeeping to work with general calendars #102

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

Merged
merged 2 commits into from
Feb 10, 2017

Conversation

xylar
Copy link
Collaborator

@xylar xylar commented Feb 6, 2017

The Date class has been replaced by a few utility functions:

  • stringToDatetime - converts an MPAS date string to a datetime.datetime
  • stringToRelativeDelta - converts an MPAS date string to an MpasRelativeDelta
  • clampToNumpyDatetime64 - clamps a datetime to the valid range supported by xarray

A new MpasRelativeDelta class has been added (deriving from dateutil.relativedelta.relativedelta)

All analysis functions and tests have been updated to work with these utility functions.

@xylar
Copy link
Collaborator Author

xylar commented Feb 6, 2017

This branch needs to be rebased after #86 gets merged.

@xylar
Copy link
Collaborator Author

xylar commented Feb 6, 2017

Testing

I tested on my laptop using a G-240km test case (and skipping regriddedSeaIceConcThick because, as usual, I don't have the mapping file).

I ran the test cases pointed to by both config.20170120.beta0.GMPAS-QU240.wolf and config.20161117.beta0.A_WCYCL1850S.ne30_oEC_ICG.edison successfully on wolf.

@xylar
Copy link
Collaborator Author

xylar commented Feb 6, 2017

This branch implements the design in #103

@pwolfram
Copy link
Contributor

pwolfram commented Feb 8, 2017

@xylar, I'll be happy to do a review following the rebase to at least give some comments.

@xylar
Copy link
Collaborator Author

xylar commented Feb 9, 2017

@pwolfram, when you get around to reviewing this PR, please take a look at the CI I added for testing my new MpasRelativeDelta class near leap days. Feel free to suggest other tests I should add if you can think of any.

return self.__neg__().__radd__(other)

def __sub__(self, other):
return NotImplemented
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason why subtraction isn't supported but addition is supported? You also have negation, which suggests subtraction should work as a combination of addition and negation (or multiplication with a negative factor), e.g., A - B = A + (-B). This should be commented or implemented.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So that's because date - offset is sensible but offset - date doesn't really make sense. So __rsub__ is okay but __sub__ should be undefined. Have I missed something? Keep in mind that addition and subtraction is with datetime.datetime objects, not with other MpasRelativeDelta objects.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would argue that the subtraction operation needs to be commutative so that offset - date should make sense because it is just offset - date = - (date - offset). This is because the negation operator is defined and I'm using standard arithmetic set operations, which I think make sense in this context and is presumed by many. Alternatively, we should issue a warning that this is not implemented, which is true in general for operators like this that seem like they should intuitively be implemented.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Negation operator on a date doesn't make sense and puts a date out of range. So I don't think either offset - date or - (date - offset) are supported. I don't believe datetime.datetime supports negation, and I think that is correct.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So I would say that offset1 - offset2 = - (offset2 - offset1) if you're using two MpasRelativeDeltas but if you're using datetime.datetime as one of the objects, that's no longer necessarily a sensible operator.

Only relative intervals (years, months, etc.) are supported and not the
absolute date specifications (year, month, etc.). Addition/subtraction
of datetime.datetime objects (but not other MpasRelativeDelta,
datetime.timedelta or other related objects) is supported.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this the case? Additional comments are needed or this restriction should be removed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think we have a use case where we need to be able to add or subtract two offsets. I'm not sure if it's allowed for timedeltas. I thought not but I'll check.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay, it looks like timedelta supports addition and subtraction of other timedeltas so I'll implement it. It doesn't seem that hard.

Copy link
Contributor

Choose a reason for hiding this comment

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

We should test all overloaded operator operations too because this is best practices and will ensure that we get what is expected.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I believe I'm testing them all at this point.

"""
Temporary function for adding an offset year and clamping a datetime to
range supported by `numpy.datetime64`.
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this issue a warning (perhaps that can be turned off via a flag) that specifies which direction the clamp, i.e., floor or ceil, was performed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see, I had misread your comment the first time. There are a lot of cases where this function is used precisely because the clamping behavior is desired, so it would be important to suppress the warning in those cases. I'm inclined not to add the complexity required to have an optional warning, given that this function really is temporary and the next PR I am planning to make will eliminate this function entirely, along with yearOffset. But I can add this functionality if you feel strongly about it.

Copy link
Contributor

Choose a reason for hiding this comment

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

If we are going to get rid of this on a short timescale of weeks, but not months, this is noise and we should proceed in the less rigorous way.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's my intention to have this replaced as soon as possible (definitely weeks rather than months). That partly depends on the speed of reviews and testing of the PRs that come before it. So I appreciate you working on this.

self.assertEqual(date1, date2)

# gregorian
calendar = 'gregorian'
Copy link
Contributor

Choose a reason for hiding this comment

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

How about looping over gregorian and gregorian_noleap?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep, I'll do that.

Copy link
Contributor

Choose a reason for hiding this comment

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

We want to test the whole range of acceptable dates for both and then the distinction between gregorian and gregorian_noleap for each distinguishing case, e.g., Feb 29.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Really? We can do that, but that seems pretty extreme to me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I guess I don't really understand what you mean by "test the whole range of acceptable dates"? I guess you mean the different string formattings? I think it's pretty redundant to check this for both calendars because the function for parsing these strings is completely independent of the calendar, it just keeps the calendar around for doing math operations later on. So in any cases where we're not doing math, the calendar is irrelevant.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

But I'm looping over both calendars anyway. So I guess this is resolved.

date2 = datetime.datetime(year=2262, month=1, day=1)
self.assertEqual(date1, date2)

# test if the calendars behave as they should
Copy link
Contributor

Choose a reason for hiding this comment

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

The loop could go here and reduce the number of lines of code required by about a factor of two.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't mind doing this and I agree it makes sense to systematically test both calendars but there will be no large reduction in code. Each test is unique (except for the first 2) independent of calendar.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, this makes sense and will clarify the code albeit not at a huge reduction in the amount of code. It will be clearer that this set of cases responds the same for each calendar and this set responds differently. Perhaps you could do something like I'll show you below in another comment... to make the distinction clearer.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay, I think this is addressed.

calendar='gregorian')
diff = date1-date2
self.assertEqual(diff, stringToDatetime('1995-12-26'))

Copy link
Contributor

Choose a reason for hiding this comment

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

I would like to see some tests on operators for MpasRelativeDelta instances.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Many of these tests already do test addition and subtraction operators. I will add some more systematic tests of all operators, though.

Copy link
Contributor

Choose a reason for hiding this comment

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

This would be good. But you could also note that a particular test is doing both in a comment too if that is cleaner.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yep, I've done so.

@pwolfram
Copy link
Contributor

pwolfram commented Feb 9, 2017

@xylar, I added some incomplete and preliminary comments on this PR to get you started.

Copy link
Contributor

@pwolfram pwolfram left a comment

Choose a reason for hiding this comment

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

I've added some preliminary comments related to time-based classes and testing. I suspect there may be some more comments following the rebase and further understanding of additional challenges with calendars.

self.assertEqual(stringToDatetime('2016-03-01') -
stringToRelativedelta('0000-00-01',
calendar='gregorian'),
stringToDatetime('2016-02-29'))
Copy link
Contributor

Choose a reason for hiding this comment

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

How about simplifying via something like:

for calendar, expected in zip(['gregorian', 'gregorian_noleap'], ['2016-03-01', '2016-02-29']):
    self.assertEqual(stringToDateTime('2016-03-01') - stringToRelativedelta('0000-00-01', calendar=calendar),
                                stringToDateTime(expected))

This more clearly shows the distinction between the calendars and their usage and reduces the amount of needed code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay, I'll do that.

@xylar xylar force-pushed the timekeeping_reorg branch from c57ef11 to 31e8742 Compare February 9, 2017 19:53
@xylar
Copy link
Collaborator Author

xylar commented Feb 9, 2017

@pwolfram, I think I've addressed all your comments for now. Why don't you wait until we can merge #86 and I can rebase before continuing your review?

@xylar xylar force-pushed the timekeeping_reorg branch from 31e8742 to 9178c1c Compare February 10, 2017 07:22
@xylar
Copy link
Collaborator Author

xylar commented Feb 10, 2017

@pwolfram and @milenaveneziani, this PR has been rebased onto develop and is ready to review. I have left on the "Don't Merge" flag because I want to squash some commits before it gets merged.

@xylar xylar force-pushed the timekeeping_reorg branch from 9178c1c to c22e6c8 Compare February 10, 2017 07:28
Remove Date class.
Update streams to use new timekeeping utility.
Update all analysis scripts to use timekeeping utility.
Update testing to new timekeeping utility funs.
@xylar
Copy link
Collaborator Author

xylar commented Feb 10, 2017

Okay, I have tested this PR on Edison (while /project is still available!) with the usual beta0 run (in configs/edison) and results are bit-for-bit to those from develop.

I squashed commits, so the PR is now ready to merge.

@pwolfram, please let me know if there are remaining concerns that I haven't addressed.

@milenaveneziani, please test and merge once you get the go-ahead from @pwolfram.

Copy link
Contributor

@pwolfram pwolfram left a comment

Choose a reason for hiding this comment

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

I have a few extremely minor comments and this is great forward progress and I'm signing-off-- LGTM.

__truediv__ = __div__

def __repr__(self):
l = []
Copy link
Contributor

Choose a reason for hiding this comment

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

This is dangerous for multiple reasons-- I would greatly prefer that we never use single-character variables unless they are an index. Furthermore, "l" looks a lot like a "1", especially in certain fonts. Cna you please make this something like rep or strrep, e.g. something three characters or more instead of l?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorry, I copied this code straight out of relativedelta and only modified the minimum to add the calendar. I agree that it's badly written. I'll clean it up.

Last modified
-------------
02/04/2017
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

An aside-- we should set something up that produces a "readthedocs" descrition of at least our public API. Feel free to do the honors for an issue or I can also do so. I'm happy to set this up when I get a a moment too if you would like so that we can leverage all your really good work getting docstrings into a more general sphinx-compatable format.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Go for it. I agree that this needs to happen but I haven't had time (and won't for awhile) to look into it further.

# YYYY-MM-DD
# SSSSS

for calendar in ['gregorian', 'gregorian_noleap']:
Copy link
Contributor

Choose a reason for hiding this comment

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

Great! Thanks @xylar :-)

date2 = datetime.datetime(year=2262, month=1, day=1)
self.assertEqual(date1, date2)

def test_MpasRelativeDeltaOps(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Great!


# make sure there's an error when we try to add MpasRelativeDeltas
# with different calendars
with self.assertRaisesRegexp(ValueError,
Copy link
Contributor

Choose a reason for hiding this comment

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

Perfect, because this enforces a design intent.

This merge adds a new MpasRelativeDelta class derived from
dateutils.relativedelta.relativedelta.  This class extends
relativedelta to handle both MPAS calendars (important for
addition to/subtraction from datetime.datetime objects).

The timekeeping tests have been updated to work with the new class
and to include new tests that ensure the right behavior for
manipulating dates near leap days.
@xylar xylar force-pushed the timekeeping_reorg branch from a44e572 to f5382cd Compare February 10, 2017 15:24
@xylar
Copy link
Collaborator Author

xylar commented Feb 10, 2017

@pwolfram, I think I've addressed your remaining comment.

@pwolfram
Copy link
Contributor

@xylar, I'm giving the go-ahead to @milenaveneziani for the merge.

@milenaveneziani
Copy link
Collaborator

I tested this on wolf using the GMPAS-240 case and including the sea-ice climos. All went well.
Here is the sea-ice concentration for JFM, for example:
iceconcnasateamnh_20170120 beta0 gmpas-qu240 wolf_jfm_years0002-0003

@milenaveneziani milenaveneziani merged commit f5382cd into MPAS-Dev:develop Feb 10, 2017
@xylar xylar deleted the timekeeping_reorg branch February 10, 2017 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants