Skip to content

Use broadcast_shapes instead of broadcast in GCRS equality checks - #20420

Open
maxnoe wants to merge 1 commit into
astropy:mainfrom
maxnoe:gcrs-broadcast-shapes
Open

maxnoe wants to merge 1 commit into
astropy:mainfrom
maxnoe:gcrs-broadcast-shapes

Conversation

@maxnoe

@maxnoe maxnoe commented Sep 17, 2026

Copy link
Copy Markdown
Member

Description

For large SkyCoord objects, e.g. many obstimes, actually performing the broadcast is a very expensive operation, checking shapes is enough.

main

observe the 200_000 and 600_000 calls on numpy / quantity methods

GCRS -> CIRS:  8.10 s
         16554063 function calls (15740490 primitive calls) in 8.092 seconds

   Ordered by: internal time
   List reduced from 1115 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    2.505    2.505    2.505    2.505 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:7029(c2i06a)
  1200252    1.097    0.000    1.169    0.000 /home/maxnoe/CTAO/astropy/astropy/units/quantity.py:654(__array_finalize__)
   600065    0.631    0.000    2.464    0.000 /home/maxnoe/CTAO/astropy/astropy/units/quantity.py:839(_new_view)
   200000    0.527    0.000    4.004    0.000 /home/maxnoe/CTAO/astropy/astropy/coordinates/representation/base.py:358(_apply)
   600070    0.477    0.000    3.047    0.000 /home/maxnoe/CTAO/astropy/astropy/units/quantity.py:1352(__getitem__)
1611189/810697    0.226    0.000    0.359    0.000 {built-in method builtins.getattr}
600901/600811    0.218    0.000    0.466    0.000 {method 'view' of 'numpy.ndarray' objects}
   600188    0.188    0.000    0.228    0.000 /home/maxnoe/CTAO/astropy/astropy/units/quantity.py:911(_set_unit)
   200000    0.183    0.000    4.211    0.000 /home/maxnoe/CTAO/astropy/astropy/coordinates/representation/base.py:1064(_apply)
    43609    0.167    0.000    0.359    0.000 /home/maxnoe/CTAO/astropy/astropy/io/ascii/fixedwidth.py:36(__call__)

GCRS -> TETE:  7.41 s
         13208213 function calls (12407737 primitive calls) in 7.405 seconds

   Ordered by: internal time
   List reduced from 553 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    2.610    2.610    2.610    2.610 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:11008(pnm06a)
  1200139    1.040    0.000    1.110    0.000 /home/maxnoe/CTAO/astropy/astropy/units/quantity.py:654(__array_finalize__)
   600050    0.619    0.000    2.391    0.000 /home/maxnoe/CTAO/astropy/astropy/units/quantity.py:839(_new_view)
   200000    0.526    0.000    3.927    0.000 /home/maxnoe/CTAO/astropy/astropy/coordinates/representation/base.py:358(_apply)
   600025    0.471    0.000    2.968    0.000 /home/maxnoe/CTAO/astropy/astropy/units/quantity.py:1352(__getitem__)
1600751/800399    0.220    0.000    0.351    0.000 {built-in method builtins.getattr}
   600152    0.217    0.000    0.460    0.000 {method 'view' of 'numpy.ndarray' objects}
   200000    0.190    0.000    4.143    0.000 /home/maxnoe/CTAO/astropy/astropy/coordinates/representation/base.py:1064(_apply)
   600090    0.187    0.000    0.227    0.000 /home/maxnoe/CTAO/astropy/astropy/units/quantity.py:911(_set_unit)
   600056    0.162    0.000    0.162    0.000 {built-in method numpy.asanyarray}

This branch

GCRS -> CIRS:  3.54 s
         3354051 function calls (3340486 primitive calls) in 3.511 seconds

   Ordered by: internal time
   List reduced from 1109 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    2.588    2.588    2.588    2.588 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:7029(c2i06a)
    43609    0.185    0.000    0.395    0.000 /home/maxnoe/CTAO/astropy/astropy/io/ascii/fixedwidth.py:36(__call__)
        2    0.136    0.068    0.830    0.415 /home/maxnoe/CTAO/astropy/astropy/io/ascii/core.py:1385(read)
   975804    0.122    0.000    0.211    0.000 /home/maxnoe/CTAO/astropy/astropy/io/ascii/core.py:410(process_val)
   976335    0.089    0.000    0.089    0.000 {method 'strip' of 'str' objects}
   982336    0.081    0.000    0.081    0.000 {method 'append' of 'list' objects}
      588    0.057    0.000    0.057    0.000 {built-in method numpy.array}
     5423    0.042    0.000    0.042    0.000 /home/maxnoe/CTAO/astropy/astropy/io/ascii/core.py:915(<genexpr>)
2760/2750    0.011    0.000    0.024    0.000 /home/maxnoe/CTAO/astropy/astropy/units/core.py:2472(_expand_and_gather)
        1    0.010    0.010    0.015    0.015 /home/maxnoe/CTAO/astropy/astropy/coordinates/earth.py:707(_get_gcrs_posvel)


GCRS -> TETE:  2.61 s
         8183 function calls (7715 primitive calls) in 2.604 seconds

   Ordered by: internal time
   List reduced from 547 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    2.465    2.465    2.465    2.465 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:11008(pnm06a)
        1    0.086    0.086    0.086    0.086 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:13441(gst06)
        1    0.009    0.009    0.011    0.011 /home/maxnoe/CTAO/astropy/astropy/coordinates/earth.py:707(_get_gcrs_posvel)
        1    0.008    0.008    0.008    0.008 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:17825(utcut1)
        1    0.007    0.007    0.007    0.007 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:17732(utctai)
       16    0.006    0.000    0.006    0.000 {function Quantity.__array_ufunc__ at 0x7fc0e446a020}
        2    0.003    0.002    0.003    0.002 {method 'searchsorted' of 'numpy.ndarray' objects}
        1    0.003    0.003    0.003    0.003 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:7684(c2tcio)
        1    0.002    0.002    0.002    0.002 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:11164(pom00)
        8    0.001    0.000    0.001    0.000 /home/maxnoe/CTAO/astropy/astropy/time/utils.py:122(two_sum)

Both #20416 and this branch merged together

GCRS -> CIRS:  0.90 s
         3354436 function calls (3340869 primitive calls) in 0.872 seconds

   Ordered by: internal time
   List reduced from 1140 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    43609    0.156    0.000    0.331    0.000 /home/maxnoe/CTAO/astropy/astropy/io/ascii/fixedwidth.py:36(__call__)
        2    0.114    0.057    0.702    0.351 /home/maxnoe/CTAO/astropy/astropy/io/ascii/core.py:1385(read)
   975804    0.101    0.000    0.175    0.000 /home/maxnoe/CTAO/astropy/astropy/io/ascii/core.py:410(process_val)
        1    0.080    0.080    0.080    0.080 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:11720(s06)
   976335    0.074    0.000    0.074    0.000 {method 'strip' of 'str' objects}
   982339    0.068    0.000    0.068    0.000 {method 'append' of 'list' objects}
      590    0.049    0.000    0.049    0.000 {built-in method numpy.array}
     5423    0.035    0.000    0.035    0.000 /home/maxnoe/CTAO/astropy/astropy/io/ascii/core.py:915(<genexpr>)
2760/2750    0.010    0.000    0.021    0.000 /home/maxnoe/CTAO/astropy/astropy/units/core.py:2472(_expand_and_gather)
      612    0.008    0.000    0.008    0.000 /home/maxnoe/CTAO/astropy/astropy/utils/data_info.py:340(__get__)


GCRS -> TETE:  0.14 s
         8556 function calls (8086 primitive calls) in 0.142 seconds

   Ordered by: internal time
   List reduced from 577 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.082    0.082    0.082    0.082 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:13441(gst06)
        1    0.008    0.008    0.008    0.008 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:17825(utcut1)
        1    0.008    0.008    0.010    0.010 /home/maxnoe/CTAO/astropy/astropy/coordinates/earth.py:707(_get_gcrs_posvel)
       16    0.006    0.000    0.006    0.000 {function Quantity.__array_ufunc__ at 0x7f8cc9b62020}
        1    0.006    0.006    0.006    0.006 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:17732(utctai)
        1    0.004    0.004    0.004    0.004 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:8202(fw2m)
        2    0.003    0.002    0.003    0.002 {method 'searchsorted' of 'numpy.ndarray' objects}
        1    0.002    0.002    0.002    0.002 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:7684(c2tcio)
        1    0.002    0.002    0.002    0.002 /home/maxnoe/CTAO/astropy/.venv/lib/python3.12/site-packages/erfa/core.py:11164(pom00)
       12    0.001    0.000    0.001    0.000 /home/maxnoe/CTAO/astropy/astropy/time/utils.py:122(two_sum)

Benchmark script:

from pstats import Stats
import cProfile
import numpy as np
from astropy import units as u
from astropy.coordinates import GeocentricTrueEcliptic, SkyCoord, ICRS, TETE, CIRS, EarthLocation, ITRS, GCRS
from astropy.time import Time
from astropy.coordinates.erfa_astrom import erfa_astrom, ErfaAstromInterpolator
from time import perf_counter

erfa_astrom.set(ErfaAstromInterpolator(5 * u.min))

n = 100_000
t0 = Time("2025-01-01")
obstime = t0 + np.linspace(0, 2, n) * u.hour

combinations = [
    (GCRS(), CIRS()),
    (GCRS(), TETE()),
]


coord = SkyCoord(
    np.linspace(0, 360, n, endpoint=False) * u.deg,
    0 * u.deg,
    obstime=obstime,
)

for from_frame, to_frame in combinations:
    from_coord = coord.transform_to(from_frame)

    t0 = perf_counter()
    with cProfile.Profile() as prof:
        to_coord = from_coord.transform_to(to_frame)
        stats = Stats(prof)

    t = perf_counter() - t0

    print(f"{from_frame.__class__.__name__} -> {to_frame.__class__.__name__}: {t:5.2f} s")
    stats.sort_stats("tottime").print_stats(10)

AI Disclosure

  • I certify that I am human and that I take full responsibility for this pull request including all interactions with reviewers.

The issue was identified by me looking at profiling output while working on #20416, the exact code location was identified by codex / gpt-6-astra, fix was made by me.

Merge method

  • By checking this box, the PR author has requested that maintainers do NOT use the "Squash and Merge" button. Maintainers should respect this when possible; however, the final decision is at the discretion of the maintainer that merges the PR.

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for rebase and squash.
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the bot.
  • Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  • Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  • At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) before merge.

@maxnoe

maxnoe commented Sep 17, 2026

Copy link
Copy Markdown
Member Author

@mhvk

@maxnoe
maxnoe force-pushed the gcrs-broadcast-shapes branch from 46d3378 to a41cc1b Compare September 17, 2026 07:16
@pllim pllim added this to the v8.1.0 milestone Sep 17, 2026
@pllim
pllim requested a review from mhvk September 17, 2026 14:32
@pllim pllim added Performance benchmark Run benchmarks for a PR labels Sep 17, 2026
@pllim

pllim commented Sep 17, 2026

Copy link
Copy Markdown
Member

Hmm benchmarks not affected. Does this mean we need a new benchmark at https://github.com/astropy/astropy-benchmarks ?

@maxnoe

maxnoe commented Sep 17, 2026

Copy link
Copy Markdown
Member Author

I don't think there are any benchmarks for this particular transform or any benchmarks for the ErfaAstromInterpolator.

I can open a merge request

@mhvk mhvk left a comment

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.

Hah, that is really an example of making the wrong inference: for ndarray, np.broadcast is the fastest way to check shapes, but if the inputs are not arrays, clearly that is no longer true. As for benchmarks, having one about GCRS equality would be nice, but I'm happy to get this in without it.

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