You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Premature broadcasting of coordinates in transforms creates slowdowns #10997
In summary, when transforming coordinate frames we broadcast before transforming, which ought not to be necessary.
In my PR to add a topocentric frame I noticed some odd behaviour. Suppose obstime is a (N, 1) array of times and we have M targets. Below I create an alt-az frame intended to broadcast against each other:
importnumpyasnpfromastropy.timeimportTimeimportastropy.unitsasufromastropy.coordinatesimportEarthLocation, CIRS, AltAz, SkyCoordfromastropy.coordinates.tests.utilsimportrandomly_sample_spherefromastropy.coordinates.erfa_astromimportErfaAstromInterpolator, erfa_astromtimes=Time('2020-08-21T00:00') +np.linspace(-3*u.hour, 3*u.hour, 1000)
location=EarthLocation.of_site('lapalma')
aa_frame=AltAz(obstime=times[:, np.newaxis], location=location)
# 1000 random locations on the skyra, dec, _=randomly_sample_sphere(200)
coos=SkyCoord(ra, dec)
Now, if I convert this to AltAz it is extremely slow. But it's much faster if I add CIRS as an intermediate step explicitly, even though CIRS is the intermediate frame between ICRS and AltAz...
coos.transform_to(aa_frame) # 18 secondscoos.transform_to(CIRS()).transform_to(aa_frame) # 1 second
Note this isn't like this in the current master, but it illustrates the issue, which I believe is this. If you run coos.transform_to(aa_frame) the first step is to make a CIRS frame. The attributes of this frame are collected from the players in the transform, so it picks up the obstime from the AltAz frame and the positions from the ICRS frame, so the resulting CIRS frame is shape (N, M). This large frame is pushed through a CIRS->CIRS' which causes the slowdown.
In the second instance the CIRS->CIRS' transform is only for a coordinate of shape (M,) which is much faster.
Although this specific example doesn't affect astropy master, it will if my PR is merged, and @mhvk points out it probably also slows down a single ICRS position -> AltAz at many times transform.
My question here is why is this CIRS->CIRS' transformation happening? It seems to me that if the source positions are picked up from the ICRS frame and the observer's location and the time of the observation are picked up from the AltAz frame, then, for each source position, the initial transform to CIRS will take into account the time of observation and the location of the observer so that the resulting CIRS frame for each source will be ready for transformation to AltAz.
It's intricately tied up with how astropy handles coordinate transforms - there's a big chain of transforms and the code doing the transformations "knows" the attributes of the various steps in the chain.
Ideally it would work as you suggest, but there are two issues:
The transformation only knows the attributes of the input coordinates and the destination frame. In this case it knows the times and the location - but it doesn't know how to use the location to create a topocentric CIRS frame (see Cannot directly transform output from GCRS to AltAz / need ITRS location? #10996). So the initial CIRS coord that gets made is geocentric.
The transformation code "knows" it will end up creating a (N, M) shaped coordinate, so it does that in the first step, making a (N, M) geocentric coordinate.
Then the code runs a check of the (N, M) obstime against the time in the altaz frame and the observatory location in the CIRS frame against the (M, 1) time of the altaz frame to see if it needs to run a conversion, sees that it does, and performs an extremely slow transform of the (N, M) geocentric CIRS frame.
There are clearly smarter ways of doing this, but actually working out how without breaking things is hard.
Assuming that it knows that its location attributes are the GCRS position and velocity of the observer, how is it that it doesn't know to call location.get_gcrs_posvel() to obtain these?
I don't think that you need to get overly general here. Given that CIRS is the designated frame for conversion to observed coordinates and, for observed coordinates, the location is specified to be an EarthLocation, CIRS should know how to obtain its location attributes from an EarthLocation.
Another possibility would be to add obsgeoloc and obsgeovel as attributes to the AltAz frame. These could be derived from the EarthLocation attribute when the frame is instantiated and passed directly to the CIRS frame instead of the EarthLocation itself.
@mkbrewer - yes, I have been thinking about adding obsgeoloc and obsgeovel as well - that seems somewhat cleaner. But I'm still thinking also about my suggestion of using a frame instance. Probably best to change the API only once!
Mostly just to keep track of where the problem originates: it is definitely inside the execution of the transformation graph, not in .transform_to(...):
Since this doesn't cause a problem in current master, it is the new presence of the observer location that is actually causing the trouble. Possibly, we should keep the geocentric CIRS so that transformations can be written differently?
EDIT: am still a bit confused: the position does not get passed on (I thought), so why for 100 positions and 100 times, is the difference like this:
In [35]: %timeit coos.frame.transform_to(CIRS(obstime=times)).transform_to(aa_frame)
120 ms ± 2.58 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [36]: %timeit coos.frame.transform_to(CIRS()).transform_to(aa_frame)
88.6 ms ± 1.14 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [37]: %timeit coos.frame.transform_to(aa_frame)
1.44 s ± 17.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
I found it illuminating to print the shape of cirs_coo as the first line of cirs_to_altaz.
Maybe there’s a smarter way of writing the transforms in my topocentric CIRS branch, but you can’t handle aberration properly unless you make a topocentric CIRS coordinate.
You can keep a geocentric only CIRS frame in astropy if you write the ICRS->AltAz transform and remove the CIRS->AltAz one (because it’s innacurate). That avoids this issue.
It does rather beg the question of why have the CIRS frame at all. You couldn’t use a geocentric CIRS frame to point a telescope accurately for example.
That transform calls cirs_to_icrs and then icrs_to_cirs, the bulk of the time is spent in the first call:
67 def cirs_to_icrs(cirs_coo, icrs_frame):
68 # set up the astrometry context for ICRS<->cirs and then convert to
69 # astrometric coordinate direction
70 1 44.0 44.0 0.0 print('cirs_to_icrs: ', cirs_coo.shape)
71 1 4264289.0 4264289.0 99.3 astrom = erfa_astrom.get().apci(cirs_coo)
Activity
mkbrewer commentedon Nov 6, 2020
My question here is why is this CIRS->CIRS' transformation happening? It seems to me that if the source positions are picked up from the ICRS frame and the observer's location and the time of the observation are picked up from the AltAz frame, then, for each source position, the initial transform to CIRS will take into account the time of observation and the location of the observer so that the resulting CIRS frame for each source will be ready for transformation to AltAz.
StuartLittlefair commentedon Nov 6, 2020
It's intricately tied up with how astropy handles coordinate transforms - there's a big chain of transforms and the code doing the transformations "knows" the attributes of the various steps in the chain.
Ideally it would work as you suggest, but there are two issues:
The transformation only knows the attributes of the input coordinates and the destination frame. In this case it knows the times and the
location- but it doesn't know how to use thelocationto create a topocentric CIRS frame (see Cannot directly transform output from GCRS to AltAz / need ITRS location? #10996). So the initial CIRS coord that gets made is geocentric.The transformation code "knows" it will end up creating a (N, M) shaped coordinate, so it does that in the first step, making a (N, M) geocentric coordinate.
Then the code runs a check of the (N, M) obstime against the time in the altaz frame and the observatory location in the CIRS frame against the (M, 1) time of the altaz frame to see if it needs to run a conversion, sees that it does, and performs an extremely slow transform of the (N, M) geocentric CIRS frame.
There are clearly smarter ways of doing this, but actually working out how without breaking things is hard.
mkbrewer commentedon Nov 6, 2020
Assuming that it knows that its location attributes are the GCRS position and velocity of the observer, how is it that it doesn't know to call
location.get_gcrs_posvel()to obtain these?mkbrewer commentedon Nov 6, 2020
I don't think that you need to get overly general here. Given that CIRS is the designated frame for conversion to observed coordinates and, for observed coordinates, the location is specified to be an
EarthLocation, CIRS should know how to obtain its location attributes from anEarthLocation.mkbrewer commentedon Nov 6, 2020
Another possibility would be to add
obsgeolocandobsgeovelas attributes to the AltAz frame. These could be derived from theEarthLocationattribute when the frame is instantiated and passed directly to the CIRS frame instead of theEarthLocationitself.mhvk commentedon Nov 6, 2020
@mkbrewer - yes, I have been thinking about adding
obsgeolocandobsgeovelas well - that seems somewhat cleaner. But I'm still thinking also about my suggestion of using a frame instance. Probably best to change the API only once!mhvk commentedon Nov 6, 2020
Mostly just to keep track of where the problem originates: it is definitely inside the execution of the transformation graph, not in
.transform_to(...):Specifically,
astropy/astropy/coordinates/transformations.py
Lines 1404 to 1422 in ab1a1d3
p.s. Oddly, the coordinates are not exactly identical either (off by ~4e-13)
mhvk commentedon Nov 6, 2020
Since this doesn't cause a problem in current master, it is the new presence of the observer location that is actually causing the trouble. Possibly, we should keep the geocentric CIRS so that transformations can be written differently?
EDIT: am still a bit confused: the position does not get passed on (I thought), so why for 100 positions and 100 times, is the difference like this:
StuartLittlefair commentedon Nov 6, 2020
Hi @mhvk
I found it illuminating to print the shape of
cirs_cooas the first line ofcirs_to_altaz.Maybe there’s a smarter way of writing the transforms in my topocentric CIRS branch, but you can’t handle aberration properly unless you make a topocentric CIRS coordinate.
You can keep a geocentric only CIRS frame in astropy if you write the ICRS->AltAz transform and remove the CIRS->AltAz one (because it’s innacurate). That avoids this issue.
It does rather beg the question of why have the CIRS frame at all. You couldn’t use a geocentric CIRS frame to point a telescope accurately for example.
StuartLittlefair commentedon Nov 6, 2020
For example, if I add print statements to the conversion routines in play I get this:
StuartLittlefair commentedon Nov 6, 2020
In terms of where the time is being spent:
That transform calls
cirs_to_icrsand thenicrs_to_cirs, the bulk of the time is spent in the first call: