Skip to content

Add BaseCoordinateFrame.cache and doc inplace modification. - #5575

Merged
eteq merged 13 commits into
astropy:masterfrom
Cadair:coordinate_cache
Dec 9, 2016
Merged

eteq merged 13 commits into
astropy:masterfrom
Cadair:coordinate_cache

Conversation

@Cadair

@Cadair Cadair commented Dec 7, 2016

Copy link
Copy Markdown
Member

This is the result of the discussion in #3797 It adds a section to the documentation about inplace modification of data and the requirements to clear the new cache when doing this.

This also refactors the cache to be a lazyproperty cache of BaseCoordinateFrame which is a defaultdict(dict) and the _rep_cache is replaced by cache['representation']

@Cadair

Cadair commented Dec 7, 2016

Copy link
Copy Markdown
Member Author

ping @taldcroft @eteq @mhvk

Comment thread docs/coordinates/inplace.rst Outdated
modifiying the values of the representation data as follows::

c = SkyCoord([1,2],[3,4], unit='deg')
c.data.lon[:] = [10, 20] * u.deg

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Use c.data.lon[()] = .. since this syntax also works for a scalar (which is a 0-d array internally).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Might want to comment on the difference between lon and ra. Then give a hint about mapping from frame attribute names to representation component names by examining c.representation_component_names.

Comment thread docs/coordinates/inplace.rst Outdated
`~astropy.coordinates.BaseCoordinateFrame.realize_frame`, this is the safest way
to change the data in a frame as it creates a new frame with that data.

Due to the fact that this creates a new frame it is quite slow, and in some

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You could leave out the quite, and perhaps even use relatively.

Comment thread docs/coordinates/inplace.rst Outdated
changes to the frame for maximum speed. This modification can be done by
modifiying the values of the representation data as follows::

c = SkyCoord([1,2],[3,4], unit='deg')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You should make these tested examples and show that it actually works.

Comment thread docs/coordinates/inplace.rst Outdated
clear this cache so future calculations of new representations are recomputed
using the new data. This can be achieved by doing::

del c.frame.cache['representation']

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you write del c.cache['representation'] then the example should work for either a SkyCoord or Frame. (I think so anway, needs to be verified).

Comment thread docs/coordinates/inplace.rst Outdated
clear this cache so future calculations of new representations are recomputed
using the new data. This can be achieved by doing::

del c.frame.cache['representation']

@taldcroft taldcroft Dec 7, 2016 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Obvious, but this PR needs tests and changelog. In general the new code looks OK to me.

@taldcroft taldcroft left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor tweaks, otherwise this looks good to merge.

Comment thread CHANGES.rst Outdated

- A new ``BaseCoordinateFrame.cache`` dictionary has been created to expose
the internal cache. This is useful when modifying representation data
inplace without using ``realize_frame`` [#5575]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Needs a period at the end of the sentence. Then add one more sentence "Added new documentation describing details of such inplace modification of an existing coordinate object." Though we don't typically mention doc updates in the changelog, in this case it seems worthwhile.

del i.cache['representation']

# Old repr and new repr should be different (this checks for removal of the cache)
assert a != repr(i)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Test that it actually worked as expected: i.ra == 10 * u.deg, i.dec == 2 * u.deg.


will not work as a different representation object is used when acessing the
aliased component names.
aliased component names. If you wish to inspect the mapping between frame

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think that splitting a sentence with a code fragment is difficult to read, and it's usually better to say what's important about a code snippet as a complete sentence prior to the snippet. So here: "... for components on the frame. For instance the following example will not work ... component names::".

Likewise in the next bit, say " ... you can look at the following dictionary::".

@taldcroft taldcroft self-assigned this Dec 8, 2016
@taldcroft

Copy link
Copy Markdown
Member

Test failure looks unrelated (network related?), I restarted the job.

@mhvk

mhvk commented Dec 8, 2016

Copy link
Copy Markdown
Contributor

@Cadair - this looks good! One comment (both for the docstring and the documentation): since cache can only contain things that can be recalculated, one can safely delete the whole cache with del sc.cache. My own sense is that we should suggest that version -- if the coordinates change, presumably everything else in the cache is invalidated too. (This is also what we do in Time.) Note that you do not need to change any code: the lazyproperty will remove the cache, and generate a new dict when sc.cache is accessed again.

@Cadair

Cadair commented Dec 8, 2016

Copy link
Copy Markdown
Member Author

@mhvk from my testing it appears that doing del SkyCoord.cache does not work whereas del BaseCoordinateFrame.cache does, presumably due to the way that the attr lookup works. I could also foresee a situation where some transformation is cached based on the frame attrs (not the data) I wonder if @eteq has any insight on that.

@Cadair

Cadair commented Dec 8, 2016

Copy link
Copy Markdown
Member Author

also ping @ejeschke / @pllim on this as it was originally crafted to enable more performant mouse over in Ginga when using the AstropyWCS2 class.

@taldcroft

Copy link
Copy Markdown
Member

@Cadair - can you confirm that the current documentation example del c.cache['representation'] works as expected for both a SkyCoord and Frame? I think that it should, and your assessment of why del c.cache does not clear the cache makes some sense (though I would not bet my house on it).

@pllim

pllim commented Dec 8, 2016

Copy link
Copy Markdown
Member

I was just an end-user in that respect, so I don't have much to add except being curious how this affects ejeschke/ginga#113, ejeschke/ginga#181, and ejeschke/ginga#254. The last time I tried to use it (which was a while ago), AstropyWCS2 threw exception on header without WCS.

@Cadair

Cadair commented Dec 8, 2016

Copy link
Copy Markdown
Member Author

@taldcroft yeah I tested it for both. And in fact the doctest does it for SkyCoord and the test does it for frame.

@pllim I will try and get back into ginga at some point and see how what I have learned from all this plays out.

@mhvk

mhvk commented Dec 8, 2016

Copy link
Copy Markdown
Contributor

@Cadair - yes, I can see that it wouldn't work -- we'd need to define a SkyCoord.__delattr__ to go with the __getattr__ that already exists. My own sense is that it is actually important to remove the whole cache -- once coordinates are changed, it is like creating a new coordinate.

@Cadair

Cadair commented Dec 8, 2016

Copy link
Copy Markdown
Member Author

@mhvk I am not sure I agree with that, a frame with data is explicitly a combination of a frame and some data, if you change the data the frame doesn't change. I can foresee a situation where we cache something related to the frame and not related to the data (i.e. a transformation matrix), this should not be deleted if the data changes.

This actually goes back to my original motivation for the previous PR, the concept to me has always been that you should be able to setup a frame and then quickly swap out the data because the frame is a representation of the system and the numbers are just points in that system. So to me the realize_frame implementation is wasting one of the conceptual advantages of the frame/data separation by always setting up a new frame with the new data, as the frame should be immutable but the data isn't.

@mhvk

mhvk commented Dec 8, 2016

Copy link
Copy Markdown
Contributor

I see your point, but was thinking more of @eteq's example of a KD-tree, which would have to be invalidated if the coordinates changed.

But my general feeling is that a cache should be very temporary (I don't like having to have one, really) and certainly one should always be free to delete it. Furthermore, how we store the representations perhaps should be an implementation detail.

@Cadair

Cadair commented Dec 8, 2016

Copy link
Copy Markdown
Member Author

I agree with the being free to delete the cache concept, I wonder maybe if we want to have a non-data related cache in the future we could call it frame_cache or something to differentiate it from a frame which is invalid when the data is changed?

I added an example of removing the whole cache.

@eteq eteq left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is looking great, but one thing is missing: the kdtree that's used in the matching code should be updated to use the cache instead of its own private variable. I can do this myself, though, if you like?

Comment thread CHANGES.rst Outdated

- A new ``BaseCoordinateFrame.cache`` dictionary has been created to expose
the internal cache. This is useful when modifying representation data
inplace without using ``realize_frame``. Additionally, documentation for

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

inplace -> in-place?

Comment thread docs/coordinates/inplace.rst Outdated
`~astropy.coordinates.BaseCoordinateFrame.realize_frame`, this is the safest way
to change the data in a frame as it creates a new frame with that data.

Due to the fact that this creates a new frame it is relatively slow, and in some

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Found this sentence very hard to understand. Maybe: "Creating a new frame can be relatively slow, however, particularly for scalar coordinates. Hence some situations may require that data by changed in-place in an already existing frame object. This modification ..."

Comment thread docs/coordinates/inplace.rst Outdated
>>> del c.frame.cache

which removes the whole cache, but can only be done on frame instances and not
`~astropy.coordinates.SkyCoord` instances.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this last clause is not needed - it makes it sounds like you can't do this with SkyCoord, while what you actually mean is just that you have to do c.frame (as the example shows). So I'd just go with "which removes the whole cache from the frame object." or similar.

Comment thread docs/coordinates/inplace.rst Outdated
This changes the longitude values for the frame, however
`~astropy.coordinates.SkyCoord` and `~astropy.coordinates.BaseCoordinateFrame`
cache representations of the data held within the frame as they are calculated
to speed up future operations of different representations of the same data. If

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also had trouble with this, both understanding what it says, and the fact that it specifically mentions ['representation'] but not anything else that might be cached. So I'd change it to:

This changes the longitude values of the frame. Unfortunately, doing just this introduces 
problems: `~astropy.coordinates.SkyCoord` and `~astropy.coordinates.BaseCoordinateFrame` 
cache various kinds of information to speed up some repeated operations.  So we need to tell 
the cache that it should be cleared so that it can be re-calculated from the new data.  
This can be achieved by doing::

  >>> del c.cache.clear()

or::

  >>> del c.frame.cache

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should advertise just one way of doing it, and I think it should be del c.cache. This should be enabled per the earlier comment from @mhvk to add a __delitem__ to SkyCoord that just does del self._sky_coord_frame.cache.

Agree with @eteq that I can't really see a use-case for users doing fine-tuned management of the cache. As developers we should at least be able to assume an all-or-nothing strategy in user cache management.

@eteq

eteq commented Dec 8, 2016 •

Copy link
Copy Markdown
Member

Oops, did my review before reading all of this... But I think the whole point of this cache is that anyone can use it, even user code (which we can sort of think of the matching stuff with the KD trees as being an example of, as it's not really a part of the frame classes themselves). I also agree one should always be free to clear the cache, but as my comment above shows, I think there's an easy way around the del sc.cache dillema: sc.cache.clear() does the same thing as deleting the cache, as it completely clears out the contents.

And although it may not be clear from my inline comment , I definitely agree with @taldcroft that 'representation' should not be special-cased. In-place operations should either clear the whole cache or none of it - anything else is very dangerous/confusing.

Also, I don't undestand this at all:

I agree with the being free to delete the cache concept, I wonder maybe if we want to have a non-data related cache in the future we could call it frame_cache or something to differentiate it from a frame which is invalid when the data is changed?

I don't think anything in the cache would ever care about the frame, right? Isn't it always only about the data?

@Cadair

Cadair commented Dec 9, 2016

Copy link
Copy Markdown
Member Author

I have made most of the changes but not added a __delitem__ to SkyCoord do we want this to replicate the __getitem__ where it just passes through to frame if it doesn't have the attribute?

I am not convinced it's needed, I actually like the .clear() approach and because we are dealing with advanced users here I am not sure it matters we are showing them two ways to zero a dict.

@Cadair

Cadair commented Dec 9, 2016

Copy link
Copy Markdown
Member Author

@eteq

I don't think anything in the cache would ever care about the frame, right? Isn't it always only about the data?

You would know much better than me, I was just hypothesising about some transformation getting setup based on frame attributes and cached for later.

The general opinion seems pretty clear and I have no concrete use case anyway, so I have flipped the docs to delete the whole dict.


assert len(i.cache['representation']) == 2

del i.cache['representation']

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just make this del i.cache for consistency?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Given what I just stated for the documentation, I think this should be i.cache.clear() instead.



def _get_cartesian_kdtree(coord, attrname_or_kdt='_kdtree', forceunit=None):
def _get_cartesian_kdtree(coord, attrname_or_kdt='kdtree', forceunit=None):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Very much a personal preference, but I'd call this argument just kdt.

@eteq eteq Dec 9, 2016 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

On reflection I agree with you @mhvk... but I'm the one to blame for this, not @Cadair ;)

i.data.lon[()] = 10*u.deg

# Clear the cache
del i.cache['representation']

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

again, just del i.cache

assert len(i.cache['representation']) == 0


def test_inplace_change():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I feel we should have a test for a SkyCoord as well.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It is tested for SkyCoord in a doctest, but I can add one here as well.

Comment thread docs/coordinates/inplace.rst Outdated

>>> c.cache.clear()

or::

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm very happy with the c.cache.clear() way of clearing the cache. I think it should be immediately obvious that this works for frames as well, so would suggest removing the alternative (one right way to do it!).

@mhvk

mhvk commented Dec 9, 2016

Copy link
Copy Markdown
Contributor

Clearing with c.cache.clear() is definitely the way to go! (and here I am not knowing that dicts have that method...). So, my minor remaining comments are mostly about doing that in the tests too, and not even mentioning that one can also delete the cache. I also think we should have at least one test using a SkyCoord -- I would do both a scalar and an array case.

@mhvk

mhvk commented Dec 9, 2016

Copy link
Copy Markdown
Contributor

p.s. I really like how this came together, from what was a bit of a kludge to something that made enough sense to help simplify the kdtree implementation!

also cache.clear() everywhere
@Cadair

Cadair commented Dec 9, 2016

Copy link
Copy Markdown
Member Author

p.s. I really like how this came together, from what was a bit of a kludge to something that made enough sense to help simplify the kdtree implementation!

Yes this did turn out quite nice, and faster than the original hack as well :)

assert i.dec == 2 * u.deg


def test_cache_clear_sc():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry to be a nit-pick, but this should go to test_skycoord.py...

@mhvk

mhvk commented Dec 9, 2016

Copy link
Copy Markdown
Contributor

All looks good to me now!

@ejeschke

ejeschke commented Dec 9, 2016

Copy link
Copy Markdown
Contributor

Very glad to see this come together and looking forward to testing it!

@eteq

eteq commented Dec 9, 2016

Copy link
Copy Markdown
Member

A few minor things, but it'll be easier for me to just do those and send them up to this branch... then will merge and backport to 1.3 (already confirmed with @astrofrog that backporting this is ok)

@eteq

eteq commented Dec 9, 2016

Copy link
Copy Markdown
Member

Everything passed, so merging!

@eteq
eteq merged commit 989b33c into astropy:master Dec 9, 2016
@Cadair
Cadair deleted the coordinate_cache branch December 9, 2016 22:53
@Cadair

Cadair commented Dec 9, 2016

Copy link
Copy Markdown
Member Author

Woop! Thanks everyone for a really constructive review!

eteq added a commit that referenced this pull request Dec 9, 2016
Add BaseCoordinateFrame.cache and doc inplace modification.
@eteq

eteq commented Dec 9, 2016

Copy link
Copy Markdown
Member

backported to 1.3.x in 950eb26

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.

6 participants